feedback-hub 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write # required for OIDC trusted publisher
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Build package
23
+ run: |
24
+ python -m pip install build
25
+ python -m build
26
+
27
+ - name: Publish to PyPI
28
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ *.db
@@ -0,0 +1,127 @@
1
+ # Integrating feedback-hub
2
+
3
+ ## ChapterForge (wxPython)
4
+
5
+ ### 1. Add to requirements
6
+
7
+ ```
8
+ feedback-hub>=1.0
9
+ ```
10
+
11
+ ### 2. Add menu item to Help menu in app.py
12
+
13
+ ```python
14
+ # In the Help menu setup:
15
+ report_item = help_menu.Append(wx.ID_ANY, "Report an Issue")
16
+ self.Bind(wx.EVT_MENU, self.on_report_issue, report_item)
17
+ ```
18
+
19
+ ### 3. Add handler
20
+
21
+ ```python
22
+ def on_report_issue(self, _event):
23
+ from pathlib import Path
24
+ from feedback_hub import load_schema
25
+ from feedback_hub.wx_dialog import FeedbackDialog
26
+
27
+ schema = load_schema(Path(__file__).parent / "schemas" / "chapterforge.json")
28
+ dlg = FeedbackDialog(
29
+ self,
30
+ schema=schema,
31
+ github_token=CHAPTERFORGE_GITHUB_TOKEN, # bundled fine-grained PAT
32
+ app_version=__version__,
33
+ )
34
+ dlg.ShowModal()
35
+ dlg.Destroy()
36
+ ```
37
+
38
+ ### 4. Token setup
39
+
40
+ Create a GitHub fine-grained PAT:
41
+ - Go to GitHub Settings > Developer Settings > Personal Access Tokens > Fine-grained
42
+ - Repository access: BITS-ACB/chapterforge only
43
+ - Permissions: Issues = Read and Write (everything else = None)
44
+ - Store as `CHAPTERFORGE_GITHUB_TOKEN` in a config or bundle in the app
45
+
46
+ ---
47
+
48
+ ## GLOW (Flask) - Non-breaking migration
49
+
50
+ ### 1. Add to requirements.txt
51
+
52
+ ```
53
+ feedback-hub>=1.0
54
+ ```
55
+
56
+ ### 2. Replace feedback.py (one line change)
57
+
58
+ ```python
59
+ # web/src/acb_large_print_web/routes/feedback.py
60
+ # Replace entire file with:
61
+ from feedback_hub.compat_glow import feedback_bp
62
+ ```
63
+
64
+ ### 3. Any code importing from support_hub still works
65
+
66
+ ```python
67
+ # This still works unchanged:
68
+ from acb_large_print_web.support_hub import create_support_issue, load_support_hub_config
69
+ # OR directly:
70
+ from feedback_hub.compat_glow import create_support_issue, load_support_hub_config
71
+ ```
72
+
73
+ ### 4. Environment variables
74
+
75
+ No changes. GLOW's existing env vars still work:
76
+ - `FEEDBACK_GITHUB_TOKEN`
77
+ - `FEEDBACK_GITHUB_REPO`
78
+ - `FEEDBACK_GITHUB_LABELS`
79
+ - `FEEDBACK_GITHUB_ASSIGNEE`
80
+ - `FEEDBACK_PASSWORD`
81
+ - `FEEDBACK_API_TOKEN`
82
+
83
+ ---
84
+
85
+ ## QUILL (wxPython)
86
+
87
+ Same pattern as ChapterForge. Replace `report_bug()` in main_frame.py:
88
+
89
+ ```python
90
+ def report_bug(self) -> None:
91
+ from pathlib import Path
92
+ from feedback_hub import load_schema
93
+ from feedback_hub.wx_dialog import FeedbackDialog
94
+
95
+ schema = load_schema(Path(__file__).parent.parent / "schemas" / "quill.json")
96
+ dlg = FeedbackDialog(
97
+ self.frame,
98
+ schema=schema,
99
+ github_token=QUILL_GITHUB_TOKEN,
100
+ app_version=__version__,
101
+ )
102
+ result = dlg.ShowModal()
103
+ dlg.Destroy()
104
+ if result == wx.ID_OK:
105
+ self._set_status("Bug report submitted")
106
+ ```
107
+
108
+ This replaces the clipboard+browser approach with direct GitHub submission.
109
+
110
+ ---
111
+
112
+ ## Token Security Notes
113
+
114
+ **Fine-grained PATs with issues:write only are safe to bundle in desktop apps.**
115
+
116
+ - Scope: issues:write on ONE repo only
117
+ - Worst-case misuse: someone files extra issues in your repo
118
+ - Cannot access code, other repos, account settings, or billing
119
+ - Rotate annually or if compromised
120
+
121
+ **Server apps (GLOW):** Token stays in `.env`, never touches clients.
122
+
123
+ **Desktop apps (ChapterForge, QUILL):** Bundle a fine-grained PAT.
124
+ ```python
125
+ # In your app's constants or settings:
126
+ CHAPTERFORGE_GITHUB_TOKEN = "github_pat_..." # issues:write on BITS-ACB/chapterforge only
127
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Blind Information Technology Solutions (BITS)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: feedback-hub
3
+ Version: 1.0.0
4
+ Summary: Multi-framework GitHub issue submission library - native UI per framework, centralized GitHub backend
5
+ Project-URL: Homepage, https://github.com/community-access/feedback-hub
6
+ Project-URL: Documentation, https://community-access.github.io/feedback-hub
7
+ Project-URL: Repository, https://github.com/community-access/feedback-hub
8
+ Project-URL: Issues, https://github.com/community-access/feedback-hub/issues
9
+ Author-email: "Blind Information Technology Solutions (BITS)" <info@chapterforge.org>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Blind Information Technology Solutions (BITS)
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Requires-Python: >=3.10
33
+ Provides-Extra: dev
34
+ Requires-Dist: flask>=3.0; extra == 'dev'
35
+ Requires-Dist: hatch; extra == 'dev'
36
+ Requires-Dist: pytest-cov; extra == 'dev'
37
+ Requires-Dist: pytest>=8.0; extra == 'dev'
38
+ Provides-Extra: flask
39
+ Requires-Dist: flask>=3.0; extra == 'flask'
40
+ Provides-Extra: wx
41
+ Requires-Dist: wxpython>=4.2; extra == 'wx'
42
+ Description-Content-Type: text/markdown
43
+
44
+ # feedback-hub
45
+
46
+ Multi-framework GitHub issue submission library. Native UI per framework, centralized GitHub backend.
47
+
48
+ - **wxPython apps** (ChapterForge, QUILL): native dialog, direct GitHub submission
49
+ - **Flask apps** (GLOW): web form, direct GitHub submission
50
+ - **CLI / headless**: function call, direct GitHub submission
51
+
52
+ See [INTEGRATING.md](INTEGRATING.md) for integration guides.
@@ -0,0 +1,9 @@
1
+ # feedback-hub
2
+
3
+ Multi-framework GitHub issue submission library. Native UI per framework, centralized GitHub backend.
4
+
5
+ - **wxPython apps** (ChapterForge, QUILL): native dialog, direct GitHub submission
6
+ - **Flask apps** (GLOW): web form, direct GitHub submission
7
+ - **CLI / headless**: function call, direct GitHub submission
8
+
9
+ See [INTEGRATING.md](INTEGRATING.md) for integration guides.
@@ -0,0 +1,38 @@
1
+ # Publishing feedback-hub to PyPI
2
+
3
+ ## Step 1 - Create a PyPI account
4
+
5
+ Go to https://pypi.org/account/register/ and create an account for the BITS org (or use an existing one). Verify your email.
6
+
7
+ ## Step 2 - Configure a Trusted Publisher (no token needed)
8
+
9
+ This is the modern approach - GitHub Actions authenticates directly with PyPI via OIDC, no API token to rotate.
10
+
11
+ 1. Go to https://pypi.org/manage/account/publishing/
12
+ 2. Under **Add a new pending publisher**, fill in:
13
+ - **PyPI project name**: `feedback-hub`
14
+ - **Owner**: `Community-Access`
15
+ - **Repository**: `feedback-hub`
16
+ - **Workflow name**: `publish.yml`
17
+ - **Environment**: `pypi`
18
+ 3. Click **Add**
19
+
20
+ ## Step 3 - Create the `pypi` environment in GitHub
21
+
22
+ 1. Go to https://github.com/Community-Access/feedback-hub/settings/environments
23
+ 2. Click **New environment**, name it `pypi`
24
+ 3. No secrets needed - the OIDC trust handles auth
25
+
26
+ ## Step 4 - Create a GitHub release to trigger the publish
27
+
28
+ ```
29
+ gh release create v1.0.0 --repo Community-Access/feedback-hub \
30
+ --title "feedback-hub 1.0.0" \
31
+ --notes "Initial public release."
32
+ ```
33
+
34
+ This triggers `publish.yml`, which builds the package and uploads it to PyPI. The package will be live at https://pypi.org/project/feedback-hub/ within a minute or two.
35
+
36
+ ## Step 5 - Fix ChapterForge CI
37
+
38
+ Once it's on PyPI, the existing `requirements.txt` entry (`feedback-hub>=1.0`) works as-is and CI will pass.
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "feedback-hub"
7
+ version = "1.0.0"
8
+ description = "Multi-framework GitHub issue submission library - native UI per framework, centralized GitHub backend"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [
12
+ { name = "Blind Information Technology Solutions (BITS)", email = "info@chapterforge.org" }
13
+ ]
14
+ requires-python = ">=3.10"
15
+ dependencies = [] # zero hard dependencies - wx and flask are optional
16
+
17
+ [project.optional-dependencies]
18
+ flask = ["flask>=3.0"]
19
+ wx = ["wxPython>=4.2"]
20
+ dev = [
21
+ "pytest>=8.0",
22
+ "pytest-cov",
23
+ "flask>=3.0",
24
+ "hatch",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/community-access/feedback-hub"
29
+ Documentation = "https://community-access.github.io/feedback-hub"
30
+ Repository = "https://github.com/community-access/feedback-hub"
31
+ Issues = "https://github.com/community-access/feedback-hub/issues"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/feedback_hub"]
35
+
36
+ [tool.pytest.ini_options]
37
+ testpaths = ["tests"]
@@ -0,0 +1,67 @@
1
+ {
2
+ "app": "ChapterForge",
3
+ "github_repo": "BITS-ACB/chapterforge",
4
+ "github_labels": ["bug", "needs-triage"],
5
+ "github_assignee": "",
6
+ "categories": [
7
+ "Bug Report",
8
+ "Feature Request",
9
+ "Accessibility Issue",
10
+ "Documentation",
11
+ "Other"
12
+ ],
13
+ "fields": [
14
+ {
15
+ "name": "summary",
16
+ "label": "Summary",
17
+ "type": "text",
18
+ "required": true,
19
+ "max_length": 120,
20
+ "placeholder": "Brief description of the issue"
21
+ },
22
+ {
23
+ "name": "message",
24
+ "label": "What happened",
25
+ "type": "textarea",
26
+ "required": true,
27
+ "max_length": 5000,
28
+ "placeholder": "Describe the issue in detail"
29
+ },
30
+ {
31
+ "name": "expected",
32
+ "label": "What you expected",
33
+ "type": "textarea",
34
+ "required": false,
35
+ "max_length": 2000
36
+ },
37
+ {
38
+ "name": "steps",
39
+ "label": "Steps to reproduce",
40
+ "type": "textarea",
41
+ "required": false,
42
+ "max_length": 2000,
43
+ "placeholder": "1. Open folder...\n2. Click Build..."
44
+ },
45
+ {
46
+ "name": "version",
47
+ "label": "ChapterForge version",
48
+ "type": "text",
49
+ "required": false,
50
+ "default": "__app_version__"
51
+ },
52
+ {
53
+ "name": "platform",
54
+ "label": "Operating system",
55
+ "type": "text",
56
+ "required": false,
57
+ "default": "__platform__"
58
+ },
59
+ {
60
+ "name": "screenreader",
61
+ "label": "Screen reader (if any)",
62
+ "type": "text",
63
+ "required": false,
64
+ "placeholder": "e.g. NVDA 2024.1, JAWS, or None"
65
+ }
66
+ ]
67
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "app": "GLOW",
3
+ "github_repo": "Community-Access/support",
4
+ "github_labels": ["needs-triage"],
5
+ "github_assignee": "",
6
+ "categories": [
7
+ "Bug Report",
8
+ "Feature Request",
9
+ "Accessibility Issue",
10
+ "Document Conversion",
11
+ "Braille",
12
+ "Documentation",
13
+ "Other"
14
+ ],
15
+ "fields": [
16
+ {
17
+ "name": "summary",
18
+ "label": "Summary",
19
+ "type": "text",
20
+ "required": true,
21
+ "max_length": 120
22
+ },
23
+ {
24
+ "name": "message",
25
+ "label": "Details",
26
+ "type": "textarea",
27
+ "required": true,
28
+ "max_length": 5000
29
+ },
30
+ {
31
+ "name": "task",
32
+ "label": "What were you trying to do",
33
+ "type": "text",
34
+ "required": false,
35
+ "max_length": 240
36
+ },
37
+ {
38
+ "name": "version",
39
+ "label": "GLOW version",
40
+ "type": "text",
41
+ "required": false,
42
+ "default": "__app_version__"
43
+ },
44
+ {
45
+ "name": "platform",
46
+ "label": "Browser / OS",
47
+ "type": "text",
48
+ "required": false,
49
+ "default": "__platform__"
50
+ }
51
+ ]
52
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "app": "Quill",
3
+ "github_repo": "Community-Access/quill",
4
+ "github_labels": ["bug", "needs-triage"],
5
+ "github_assignee": "",
6
+ "categories": [
7
+ "Bug Report",
8
+ "Feature Request",
9
+ "Accessibility Issue",
10
+ "Documentation",
11
+ "Other"
12
+ ],
13
+ "fields": [
14
+ {
15
+ "name": "summary",
16
+ "label": "Summary",
17
+ "type": "text",
18
+ "required": true,
19
+ "max_length": 120,
20
+ "default": "Bug report"
21
+ },
22
+ {
23
+ "name": "message",
24
+ "label": "What happened",
25
+ "type": "textarea",
26
+ "required": true,
27
+ "max_length": 5000
28
+ },
29
+ {
30
+ "name": "expected",
31
+ "label": "What you expected",
32
+ "type": "textarea",
33
+ "required": false,
34
+ "max_length": 2000
35
+ },
36
+ {
37
+ "name": "steps",
38
+ "label": "Steps to reproduce",
39
+ "type": "textarea",
40
+ "required": false,
41
+ "max_length": 2000
42
+ },
43
+ {
44
+ "name": "version",
45
+ "label": "Quill version",
46
+ "type": "text",
47
+ "required": false,
48
+ "default": "__app_version__"
49
+ },
50
+ {
51
+ "name": "platform",
52
+ "label": "Operating system",
53
+ "type": "text",
54
+ "required": false,
55
+ "default": "__platform__"
56
+ },
57
+ {
58
+ "name": "screenreader",
59
+ "label": "Screen reader (if any)",
60
+ "type": "text",
61
+ "required": false,
62
+ "placeholder": "e.g. NVDA 2024.1, JAWS, or None"
63
+ }
64
+ ]
65
+ }
@@ -0,0 +1,117 @@
1
+ """feedback-hub - Multi-framework GitHub issue submission library.
2
+
3
+ Each app uses its native UI; all submit directly to GitHub Issues.
4
+
5
+ Quick start (wxPython)::
6
+
7
+ from feedback_hub import load_schema
8
+ from feedback_hub.wx_dialog import FeedbackDialog
9
+
10
+ schema = load_schema({"app": "MyApp", "github_repo": "org/repo", "fields": [...]})
11
+ dlg = FeedbackDialog(parent, schema=schema, github_token=TOKEN)
12
+ dlg.ShowModal()
13
+ dlg.Destroy()
14
+
15
+ Quick start (Flask)::
16
+
17
+ from feedback_hub.flask_blueprint import make_blueprint
18
+
19
+ feedback_bp = make_blueprint(app_name="MyApp", github_repo="org/repo")
20
+ app.register_blueprint(feedback_bp, url_prefix="/feedback")
21
+
22
+ Quick start (headless / CLI)::
23
+
24
+ from feedback_hub import submit
25
+
26
+ issue_url, error = submit(
27
+ app="MyApp",
28
+ github_repo="org/repo",
29
+ github_token=TOKEN,
30
+ summary="Something is broken",
31
+ message="Details here",
32
+ category="Bug Report",
33
+ )
34
+ """
35
+ from feedback_hub._github import GitHubConfig, resolve_token
36
+ from feedback_hub._schema import AppSchema, FieldSchema, build_entry, load_schema
37
+ from feedback_hub._storage import list_all, save
38
+
39
+ __version__ = "1.0.0"
40
+
41
+ __all__ = [
42
+ "AppSchema",
43
+ "FieldSchema",
44
+ "GitHubConfig",
45
+ "build_entry",
46
+ "list_all",
47
+ "load_schema",
48
+ "resolve_token",
49
+ "save",
50
+ "submit",
51
+ ]
52
+
53
+
54
+ def submit(
55
+ *,
56
+ app: str,
57
+ github_repo: str,
58
+ github_token: str = "",
59
+ summary: str = "",
60
+ message: str,
61
+ category: str = "feedback",
62
+ name: str = "",
63
+ email: str = "",
64
+ app_version: str = "",
65
+ github_labels: list[str] | None = None,
66
+ github_assignee: str = "",
67
+ metadata: dict | None = None,
68
+ db_path=None,
69
+ ) -> tuple:
70
+ """Headless submission -- no UI required.
71
+
72
+ Returns ``(issue_url, error_message)``.
73
+ """
74
+ from datetime import UTC, datetime
75
+ from pathlib import Path
76
+
77
+ from feedback_hub._github import create_issue
78
+ from feedback_hub._storage import save as _save, update_github_sync
79
+
80
+ token = resolve_token(github_token)
81
+ entry = {
82
+ "app": app,
83
+ "version": app_version,
84
+ "platform": "",
85
+ "category": category,
86
+ "name": name,
87
+ "email": email,
88
+ "summary": summary,
89
+ "message": message,
90
+ "metadata": metadata,
91
+ "timestamp": datetime.now(UTC).isoformat(),
92
+ }
93
+
94
+ _db = db_path or Path.home() / ".local" / "share" / "feedback-hub" / "feedback.db"
95
+ try:
96
+ row_id = _save(entry, Path(_db))
97
+ except Exception:
98
+ row_id = None
99
+
100
+ if not token:
101
+ return None, "GitHub token not configured"
102
+
103
+ cfg = GitHubConfig(
104
+ token=token,
105
+ repo=github_repo,
106
+ assignee=github_assignee,
107
+ labels=github_labels or ["needs-triage"],
108
+ )
109
+ number, url, error = create_issue(entry, cfg)
110
+
111
+ if row_id is not None:
112
+ try:
113
+ update_github_sync(row_id, issue_number=number, issue_url=url, error=error, db_path=Path(_db))
114
+ except Exception:
115
+ pass
116
+
117
+ return url, error