braindump-ai 0.1.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.
- braindump_ai-0.1.0/.claude/agents/feature-development.md +0 -0
- braindump_ai-0.1.0/.claude/settings.json +14 -0
- braindump_ai-0.1.0/.claude/skills/code-quality/SKILL.md +37 -0
- braindump_ai-0.1.0/.claude/skills/package-structure/SKILL.md +25 -0
- braindump_ai-0.1.0/.claude/skills/python-dev/SKILL.md +170 -0
- braindump_ai-0.1.0/.claude/skills/python-dev/python_template.md +24 -0
- braindump_ai-0.1.0/.claude/skills/react-dev/SKILL.md +175 -0
- braindump_ai-0.1.0/.github/workflows/pr.yml +66 -0
- braindump_ai-0.1.0/.github/workflows/publish.yml +57 -0
- braindump_ai-0.1.0/.gitignore +215 -0
- braindump_ai-0.1.0/.python-version +1 -0
- braindump_ai-0.1.0/CLAUDE.md +120 -0
- braindump_ai-0.1.0/LICENSE +201 -0
- braindump_ai-0.1.0/PKG-INFO +113 -0
- braindump_ai-0.1.0/README.md +98 -0
- braindump_ai-0.1.0/frontend/eslint.config.js +20 -0
- braindump_ai-0.1.0/frontend/index.html +13 -0
- braindump_ai-0.1.0/frontend/package-lock.json +3088 -0
- braindump_ai-0.1.0/frontend/package.json +34 -0
- braindump_ai-0.1.0/frontend/src/App.css +263 -0
- braindump_ai-0.1.0/frontend/src/App.tsx +316 -0
- braindump_ai-0.1.0/frontend/src/api.ts +150 -0
- braindump_ai-0.1.0/frontend/src/assets/favicon.png +0 -0
- braindump_ai-0.1.0/frontend/src/assets/logo.png +0 -0
- braindump_ai-0.1.0/frontend/src/components/ErrorToast.css +109 -0
- braindump_ai-0.1.0/frontend/src/components/ErrorToast.tsx +64 -0
- braindump_ai-0.1.0/frontend/src/components/GraphView.css +36 -0
- braindump_ai-0.1.0/frontend/src/components/GraphView.tsx +201 -0
- braindump_ai-0.1.0/frontend/src/components/HierarchyView.css +132 -0
- braindump_ai-0.1.0/frontend/src/components/HierarchyView.tsx +209 -0
- braindump_ai-0.1.0/frontend/src/components/MarkdownPreview.css +48 -0
- braindump_ai-0.1.0/frontend/src/components/MarkdownPreview.tsx +58 -0
- braindump_ai-0.1.0/frontend/src/components/QueryBar.css +312 -0
- braindump_ai-0.1.0/frontend/src/components/QueryBar.tsx +180 -0
- braindump_ai-0.1.0/frontend/src/components/SearchBar.css +42 -0
- braindump_ai-0.1.0/frontend/src/components/SearchBar.tsx +23 -0
- braindump_ai-0.1.0/frontend/src/components/SpikeDetail.css +125 -0
- braindump_ai-0.1.0/frontend/src/components/SpikeDetail.tsx +91 -0
- braindump_ai-0.1.0/frontend/src/components/SpikeEditor.css +171 -0
- braindump_ai-0.1.0/frontend/src/components/SpikeEditor.tsx +153 -0
- braindump_ai-0.1.0/frontend/src/components/SpikeList.css +96 -0
- braindump_ai-0.1.0/frontend/src/components/SpikeList.tsx +39 -0
- braindump_ai-0.1.0/frontend/src/components/StatusBar.css +247 -0
- braindump_ai-0.1.0/frontend/src/components/StatusBar.tsx +224 -0
- braindump_ai-0.1.0/frontend/src/components/TagsInput.css +96 -0
- braindump_ai-0.1.0/frontend/src/components/TagsInput.tsx +116 -0
- braindump_ai-0.1.0/frontend/src/main.tsx +10 -0
- braindump_ai-0.1.0/frontend/src/types.ts +29 -0
- braindump_ai-0.1.0/frontend/src/utils.ts +7 -0
- braindump_ai-0.1.0/frontend/src/vite-env.d.ts +16 -0
- braindump_ai-0.1.0/frontend/tsconfig.json +17 -0
- braindump_ai-0.1.0/frontend/vite.config.ts +29 -0
- braindump_ai-0.1.0/pyproject.toml +101 -0
- braindump_ai-0.1.0/src/braindump/__init__.py +13 -0
- braindump_ai-0.1.0/src/braindump/app.py +589 -0
- braindump_ai-0.1.0/src/braindump/dirs.py +115 -0
- braindump_ai-0.1.0/src/braindump/health.py +226 -0
- braindump_ai-0.1.0/src/braindump/llm.py +187 -0
- braindump_ai-0.1.0/src/braindump/main.py +259 -0
- braindump_ai-0.1.0/src/braindump/query.py +204 -0
- braindump_ai-0.1.0/src/braindump/storage.py +415 -0
- braindump_ai-0.1.0/src/braindump/txlog.py +209 -0
- braindump_ai-0.1.0/src/braindump/types.py +198 -0
- braindump_ai-0.1.0/src/braindump/wiki.py +742 -0
- braindump_ai-0.1.0/tests/conftest.py +59 -0
- braindump_ai-0.1.0/tests/test_app.py +265 -0
- braindump_ai-0.1.0/tests/test_health.py +141 -0
- braindump_ai-0.1.0/tests/test_llm.py +216 -0
- braindump_ai-0.1.0/tests/test_main.py +83 -0
- braindump_ai-0.1.0/tests/test_query.py +118 -0
- braindump_ai-0.1.0/tests/test_storage.py +470 -0
- braindump_ai-0.1.0/tests/test_wiki.py +356 -0
- braindump_ai-0.1.0/tools/check.py +150 -0
- braindump_ai-0.1.0/tools/hatch_build.py +39 -0
- braindump_ai-0.1.0/uv.lock +1048 -0
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(uv run ruff:*)",
|
|
5
|
+
"Bash(uv run ty:*)",
|
|
6
|
+
"Bash(uv run pytest:*)",
|
|
7
|
+
"Bash(uv run bandit:*)",
|
|
8
|
+
"Bash(uv run tools/check.py:*)",
|
|
9
|
+
"Bash(npx tsc:*)",
|
|
10
|
+
"Bash(npm run lint:*)",
|
|
11
|
+
"Bash(npm run build:*)"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-quality
|
|
3
|
+
description: >
|
|
4
|
+
Activate this skill for any task updating the source code.
|
|
5
|
+
|
|
6
|
+
This includes:
|
|
7
|
+
- creating a new source file
|
|
8
|
+
- updating an existing source file
|
|
9
|
+
- writing new tests
|
|
10
|
+
- update existing tests
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Code Quality Guardian
|
|
14
|
+
|
|
15
|
+
This sections summarizes the checks that must pass before finalizing a code change.
|
|
16
|
+
|
|
17
|
+
## Software Quality Gate
|
|
18
|
+
|
|
19
|
+
There is a single entry point to run all SW quality gates for this project:
|
|
20
|
+
|
|
21
|
+
- [ ] **Software Quality Gate**: ``uv run tools/check.py``
|
|
22
|
+
|
|
23
|
+
The individual commands for finer-grain control are listed in the subsequent subsections.
|
|
24
|
+
|
|
25
|
+
## Python Checklist
|
|
26
|
+
|
|
27
|
+
- [ ] **Python Formatting**: ``uv run ruff format``
|
|
28
|
+
- [ ] **Python Linting**: ``uv run ruff check``
|
|
29
|
+
- [ ] **Python Type Checking**: ``uv run ty check``
|
|
30
|
+
- [ ] **Python Tests**: ``uv run pytest tests``
|
|
31
|
+
- [ ] **Python Security**: ``uv run bandit``
|
|
32
|
+
|
|
33
|
+
## React and Typescript
|
|
34
|
+
|
|
35
|
+
- [ ] **TS Type Checking**: `cd frontend && npx tsc --noEmit`
|
|
36
|
+
- [ ] **Linting**: `cd frontend && npm run lint`
|
|
37
|
+
- [ ] **Build**: `cd frontend && npm run build`
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: package-structure
|
|
3
|
+
description: >
|
|
4
|
+
Activate this skill for any task adding new files to the repository.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Package Structure
|
|
8
|
+
|
|
9
|
+
## Project structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
braindump/
|
|
13
|
+
├── src/braindump/ # Python backend package (FastAPI, RAG pipeline, LLM backends)
|
|
14
|
+
├── frontend/
|
|
15
|
+
│ └── src/
|
|
16
|
+
│ └── components/ # React components (one .tsx + .css pair per component)
|
|
17
|
+
├── tests/ # Pytest integration tests
|
|
18
|
+
├── tools/ # Build hooks and dev helper scripts
|
|
19
|
+
└── pyproject.toml # Package config, hatch build, ruff, ty, bandit
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Key conventions
|
|
23
|
+
|
|
24
|
+
- New backend modules go in `src/braindump/`; register new API routes in `app.py`.
|
|
25
|
+
- New React components go in `frontend/src/components/` with a matching `.css` file.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-dev
|
|
3
|
+
description: >
|
|
4
|
+
Activate this skill for any task involving writing Python source code (extension: .py)
|
|
5
|
+
|
|
6
|
+
This includes:
|
|
7
|
+
- writing new module
|
|
8
|
+
- fixing bugs
|
|
9
|
+
- adding type hints
|
|
10
|
+
- writing docstrings
|
|
11
|
+
- creating unit tests
|
|
12
|
+
- structuring packages
|
|
13
|
+
- reviewing Python code.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Python Development Skill
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
1. **Always write idiomatic Python** — follow PEP 8 and PEP 20.
|
|
21
|
+
2. **Prefer clarity over cleverness** — readable code is maintainable code.
|
|
22
|
+
3. **Type hints by default** — add type hints for all functions.
|
|
23
|
+
4. **Fail loudly** — raise specific exceptions with helpful messages rather than silently swallowing errors.
|
|
24
|
+
5. **Test-first mindset** — suggest or produce `pytest` tests alongside non-trivial implementations.
|
|
25
|
+
6. **Assert code** — use `assert` statements only for internal assertions and type deductions.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## General Python Coding Guidelines
|
|
30
|
+
|
|
31
|
+
Before finalising any Python output, verify:
|
|
32
|
+
|
|
33
|
+
- [ ] **File Structure** — use the Python structure from [python_template.md](python_template.md)
|
|
34
|
+
- [ ] **Identifier** — use snake_case names, use a leading underscore for private constants and functions
|
|
35
|
+
- [ ] **PEP 8 compliant** — 4-space indent
|
|
36
|
+
- [ ] **Type-annotated** — all function signatures have parameter and return types
|
|
37
|
+
- [ ] **Docstrings** — public functions/classes have Google-style
|
|
38
|
+
- [ ] **Error handling** — no bare `except:`, raise specific exception types
|
|
39
|
+
- [ ] **No mutable defaults** — never `def f(x=[]):`, use `None` sentinel instead
|
|
40
|
+
- [ ] **f-strings over %/format** — prefer f-strings
|
|
41
|
+
- [ ] **Context managers** — use `with` for files, DB connections, locks
|
|
42
|
+
- [ ] **Init Files** — use ``__init__.py`` files only for exposing constants and functions, do not add code
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Test Guidelines
|
|
46
|
+
|
|
47
|
+
- [ ] **Test Functions** — use free-floating test functions instead of nested functions in test classes
|
|
48
|
+
- [ ] **Public Functions** — write tests for new public functions
|
|
49
|
+
- [ ] **Private Functions** — write tests for private functions if suitable to reduce test variation on the public function level
|
|
50
|
+
- [ ] **Test Updates** — modify existing tests as least as possible to assure software stability
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Project-Specific Guidelines
|
|
54
|
+
|
|
55
|
+
Use these guidelines in addition to the general coding guidelines:
|
|
56
|
+
|
|
57
|
+
- [ ] **Pydantic** — use `pydantic` types for file I/O and for `fastapi` routes.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Patterns & Best Practices
|
|
62
|
+
|
|
63
|
+
### Idiomatic Constructs
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
# Prefer enumerate over manual indexing
|
|
67
|
+
for i, item in enumerate(items):
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
# Prefer unpacking over indexing
|
|
71
|
+
first, *rest = collection
|
|
72
|
+
|
|
73
|
+
# Use walrus operator for assignment in conditions (Python 3.8+)
|
|
74
|
+
if m := pattern.match(line):
|
|
75
|
+
process(m.group(0))
|
|
76
|
+
|
|
77
|
+
# Prefer dataclasses for plain data containers
|
|
78
|
+
from dataclasses import dataclass, field
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class Config:
|
|
82
|
+
host: str = "localhost"
|
|
83
|
+
port: int = 8080
|
|
84
|
+
tags: list[str] = field(default_factory=list)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Error Handling
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
# Always be specific
|
|
91
|
+
try:
|
|
92
|
+
result = risky_call()
|
|
93
|
+
except (ValueError, KeyError) as exc:
|
|
94
|
+
raise RuntimeError(f"Failed to process item: {exc}") from exc
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Typing Patterns
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
|
|
101
|
+
from typing import TYPE_CHECKING
|
|
102
|
+
|
|
103
|
+
if TYPE_CHECKING:
|
|
104
|
+
from collections.abc import Sequence
|
|
105
|
+
|
|
106
|
+
def process(items: Sequence[str]) -> dict[str, int]:
|
|
107
|
+
return {item: len(item) for item in items}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### File I/O
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# Always specify encoding
|
|
114
|
+
file_path.read_text(encoding="utf-8")
|
|
115
|
+
|
|
116
|
+
# Use pathlib over os.path
|
|
117
|
+
from pathlib import Path
|
|
118
|
+
|
|
119
|
+
config_path = Path(__file__).parent / "config.yaml"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Testing
|
|
125
|
+
|
|
126
|
+
Default test framework: **pytest**. Place tests in `tests/` mirroring the source layout.
|
|
127
|
+
Test files start with `test_<module>.py`.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# tests/test_example.py
|
|
131
|
+
import pytest
|
|
132
|
+
from mymodule import my_function
|
|
133
|
+
|
|
134
|
+
def test_happy_path():
|
|
135
|
+
assert my_function("input") == "expected"
|
|
136
|
+
|
|
137
|
+
def test_raises_on_bad_input():
|
|
138
|
+
with pytest.raises(ValueError, match="must be positive"):
|
|
139
|
+
my_function(-1)
|
|
140
|
+
|
|
141
|
+
@pytest.mark.parametrize("val,expected", [
|
|
142
|
+
("a", 1),
|
|
143
|
+
("ab", 2),
|
|
144
|
+
("", 0),
|
|
145
|
+
])
|
|
146
|
+
def test_parametrized(val, expected):
|
|
147
|
+
assert len(val) == expected
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Run tests:
|
|
151
|
+
```bash
|
|
152
|
+
uv run python -m pytest -v
|
|
153
|
+
# With coverage:
|
|
154
|
+
uv run python -m pytest --cov=src --cov-report=term-missing
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Debugging Tips
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Run with verbose traceback
|
|
163
|
+
python -m traceback script.py
|
|
164
|
+
|
|
165
|
+
# Quick REPL inspection
|
|
166
|
+
python -c "import module; print(dir(module))"
|
|
167
|
+
|
|
168
|
+
# Profile a script
|
|
169
|
+
python -m cProfile -s cumulative script.py | head -20
|
|
170
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Python Template File
|
|
2
|
+
|
|
3
|
+
The line width is 120 characters.
|
|
4
|
+
The file layout shall correspond to:
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
<License Header>
|
|
8
|
+
<Includes>
|
|
9
|
+
|
|
10
|
+
#######################################################################################################################
|
|
11
|
+
# Public Interface #
|
|
12
|
+
#######################################################################################################################
|
|
13
|
+
|
|
14
|
+
<Public Interface>
|
|
15
|
+
<Public Constants>
|
|
16
|
+
<Public Functions>
|
|
17
|
+
|
|
18
|
+
#######################################################################################################################
|
|
19
|
+
# Implementation #
|
|
20
|
+
#######################################################################################################################
|
|
21
|
+
|
|
22
|
+
<Implementation>
|
|
23
|
+
<Private Constants>
|
|
24
|
+
<Private Functions>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-dev
|
|
3
|
+
description: >
|
|
4
|
+
Activate this skill for any task involving writing React/TypeScript source code
|
|
5
|
+
in the frontend/ directory.
|
|
6
|
+
|
|
7
|
+
This includes:
|
|
8
|
+
- adding new components
|
|
9
|
+
- fixing bugs
|
|
10
|
+
- adding types
|
|
11
|
+
- updating styles
|
|
12
|
+
- writing or updating tests
|
|
13
|
+
- reviewing frontend code
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# React Development Skill
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
1. **TypeScript only** — no `.js` or `.jsx` files; every file is `.ts` or `.tsx`.
|
|
21
|
+
2. **Strict mode** — `tsconfig.json` enables `"strict": true`; never use `any` unless absolutely unavoidable and always add a comment explaining why.
|
|
22
|
+
3. **Functional components only** — no class components; use hooks for all state and side-effects.
|
|
23
|
+
4. **Co-locate styles** — every component has a matching `.css` file; no inline `style={{}}` except for dynamic values that cannot be expressed in CSS.
|
|
24
|
+
5. **Fail visibly** — surface errors through `useErrorToast()` so users always see what went wrong.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## General Coding Guidelines
|
|
29
|
+
|
|
30
|
+
Before finalising any frontend output, verify:
|
|
31
|
+
|
|
32
|
+
- [ ] **File extension** — `.ts` for pure logic, `.tsx` for JSX-containing files
|
|
33
|
+
- [ ] **No JS files** — `eslint.config.js` is the only `.js` file and is tooling config, not app code
|
|
34
|
+
- [ ] **Named exports** — prefer named exports for components; default export only for the component itself (required by react-refresh)
|
|
35
|
+
- [ ] **Typed props** — every component has an explicit `interface Props { … }` or inline type
|
|
36
|
+
- [ ] **No `any`** — use `unknown` + narrowing, generics, or precise union types instead
|
|
37
|
+
- [ ] **Hook rules** — hooks called unconditionally at the top of the component; never inside loops, conditions, or nested functions
|
|
38
|
+
- [ ] **Exhaustive deps** — `useEffect` / `useCallback` / `useMemo` dependency arrays are complete; add a comment if a dep is intentionally omitted
|
|
39
|
+
- [ ] **Event handlers** — typed with `React.MouseEvent`, `React.ChangeEvent<HTMLInputElement>`, etc.
|
|
40
|
+
- [ ] **Keys** — list renders use stable, unique keys (IDs, not array indices)
|
|
41
|
+
- [ ] **Accessibility** — interactive elements have `aria-label` or visible label; icon-only buttons have `title` and `aria-label`
|
|
42
|
+
- [ ] **CSS class naming** — kebab-case; scoped with a component prefix (e.g. `.query-bar`, `.query-input`)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Project-Specific Guidelines
|
|
47
|
+
|
|
48
|
+
- [ ] **API calls** — always go through `api.ts`; never call `fetch` directly in a component
|
|
49
|
+
- [ ] **Error handling** — wrap async calls in `try/catch` and call `pushError()` from `useErrorToast()`
|
|
50
|
+
- [ ] **Types** — shared domain types live in `types.ts`; API-response shapes live in `api.ts` alongside the call that returns them
|
|
51
|
+
- [ ] **State lifting** — local state stays local; lift only when two or more siblings need it
|
|
52
|
+
- [ ] **No prop drilling past two levels** — use React context (following the `ErrorToast` provider pattern) for cross-cutting concerns
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Patterns & Best Practices
|
|
57
|
+
|
|
58
|
+
### Component structure
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// components/MyWidget.tsx
|
|
62
|
+
import { useState, useCallback } from 'react'
|
|
63
|
+
import { useErrorToast } from './ErrorToast'
|
|
64
|
+
import './MyWidget.css'
|
|
65
|
+
|
|
66
|
+
interface Props {
|
|
67
|
+
label: string
|
|
68
|
+
onConfirm: (value: string) => void
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default function MyWidget({ label, onConfirm }: Props) {
|
|
72
|
+
const { pushError } = useErrorToast()
|
|
73
|
+
const [value, setValue] = useState('')
|
|
74
|
+
|
|
75
|
+
const handleSubmit = useCallback(async () => {
|
|
76
|
+
try {
|
|
77
|
+
await someApiCall(value)
|
|
78
|
+
onConfirm(value)
|
|
79
|
+
} catch (err: unknown) {
|
|
80
|
+
pushError('Failed to submit', String(err))
|
|
81
|
+
}
|
|
82
|
+
}, [value, onConfirm])
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="my-widget">
|
|
86
|
+
<label className="my-widget-label">{label}</label>
|
|
87
|
+
<input
|
|
88
|
+
className="my-widget-input"
|
|
89
|
+
value={value}
|
|
90
|
+
onChange={e => setValue(e.target.value)}
|
|
91
|
+
onKeyDown={e => e.key === 'Enter' && handleSubmit()}
|
|
92
|
+
/>
|
|
93
|
+
<button className="my-widget-btn" onClick={handleSubmit}>
|
|
94
|
+
Confirm
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Typing async state
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
// Explicit loading / error / data pattern
|
|
105
|
+
const [data, setData] = useState<Spike[] | null>(null)
|
|
106
|
+
const [loading, setLoading] = useState(false)
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
let cancelled = false
|
|
110
|
+
setLoading(true)
|
|
111
|
+
fetchSpikes()
|
|
112
|
+
.then(spikes => { if (!cancelled) setData(spikes) })
|
|
113
|
+
.catch(err => pushError('Load failed', String(err)))
|
|
114
|
+
.finally(() => { if (!cancelled) setLoading(false) })
|
|
115
|
+
return () => { cancelled = true }
|
|
116
|
+
}, [])
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Context provider pattern
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
// Follow the ErrorToast pattern for cross-cutting concerns:
|
|
123
|
+
// 1. Define the context value interface
|
|
124
|
+
// 2. Create the context with a safe no-op default
|
|
125
|
+
// 3. Export a custom hook `useFoo()`
|
|
126
|
+
// 4. Export a `FooProvider` that wraps children
|
|
127
|
+
|
|
128
|
+
interface FooContextValue { doSomething: () => void }
|
|
129
|
+
const FooContext = createContext<FooContextValue>({ doSomething: () => {} })
|
|
130
|
+
export function useFoo() { return useContext(FooContext) }
|
|
131
|
+
export function FooProvider({ children }: { children: React.ReactNode }) { … }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Adding an API call
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// api.ts — add alongside the function that uses the new shape
|
|
138
|
+
export interface MyNewResponse {
|
|
139
|
+
id: string
|
|
140
|
+
value: number
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function fetchMyThing(id: string): Promise<MyNewResponse> {
|
|
144
|
+
return request<MyNewResponse>(`/my-thing/${id}`)
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Tooling
|
|
151
|
+
|
|
152
|
+
| Tool | Purpose | Run |
|
|
153
|
+
|------|---------|-----|
|
|
154
|
+
| **Vite** | Dev server (port 5173) + production build | `npm run dev` / `npm run build` |
|
|
155
|
+
| **TypeScript** | Type checking | `npx tsc --noEmit` |
|
|
156
|
+
| **ESLint** | Linting (typescript-eslint + react-hooks + react-refresh) | `npm run lint` |
|
|
157
|
+
|
|
158
|
+
The Vite dev server proxies `/api` → `http://localhost:8000` and `/api` websockets, so the backend must be running on port 8000 during development.
|
|
159
|
+
|
|
160
|
+
Production assets are compiled into `frontend/dist/` and bundled into the wheel by `tools/hatch_build.py`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Debugging Tips
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Type-check without building
|
|
168
|
+
npx tsc --noEmit
|
|
169
|
+
|
|
170
|
+
# Lint the source
|
|
171
|
+
npm run lint
|
|
172
|
+
|
|
173
|
+
# Inspect the production bundle contents
|
|
174
|
+
npx vite-bundle-visualizer # or: unzip -l ../dist/*.whl | grep frontend
|
|
175
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: Pull Request
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
backend:
|
|
12
|
+
name: Backend
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
timeout-minutes: 15
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v5
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.13"
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: uv sync --all-groups
|
|
28
|
+
|
|
29
|
+
- name: Lint (ruff)
|
|
30
|
+
run: uv run ruff check src/ tests/
|
|
31
|
+
|
|
32
|
+
- name: Format check (ruff)
|
|
33
|
+
run: uv run ruff format --check src/ tests/
|
|
34
|
+
|
|
35
|
+
- name: Type check (ty)
|
|
36
|
+
run: uv run ty check src/
|
|
37
|
+
|
|
38
|
+
- name: Security scan (bandit)
|
|
39
|
+
run: uv run bandit -r src/ -c pyproject.toml
|
|
40
|
+
|
|
41
|
+
- name: Tests (pytest)
|
|
42
|
+
run: uv run pytest tests/
|
|
43
|
+
|
|
44
|
+
frontend:
|
|
45
|
+
name: Frontend
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
timeout-minutes: 10
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
|
|
51
|
+
- name: Set up Node.js
|
|
52
|
+
uses: actions/setup-node@v4
|
|
53
|
+
with:
|
|
54
|
+
node-version: "20"
|
|
55
|
+
|
|
56
|
+
- name: Install dependencies
|
|
57
|
+
working-directory: frontend
|
|
58
|
+
run: npm ci
|
|
59
|
+
|
|
60
|
+
- name: Lint (eslint)
|
|
61
|
+
working-directory: frontend
|
|
62
|
+
run: npm run lint
|
|
63
|
+
|
|
64
|
+
- name: Type check & build (tsc + vite)
|
|
65
|
+
working-directory: frontend
|
|
66
|
+
run: npm run build
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write # required for PyPI trusted publishing (OIDC)
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Build
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
timeout-minutes: 15
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Node.js
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: "20"
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v5
|
|
26
|
+
|
|
27
|
+
- name: Set up Python
|
|
28
|
+
uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.13"
|
|
31
|
+
|
|
32
|
+
- name: Build wheel and sdist
|
|
33
|
+
run: uv build
|
|
34
|
+
|
|
35
|
+
- name: Upload dist artifacts
|
|
36
|
+
uses: actions/upload-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
publish:
|
|
42
|
+
name: Publish to PyPI
|
|
43
|
+
needs: build
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
timeout-minutes: 10
|
|
46
|
+
environment:
|
|
47
|
+
name: pypi
|
|
48
|
+
url: https://pypi.org/p/braindump
|
|
49
|
+
steps:
|
|
50
|
+
- name: Download dist artifacts
|
|
51
|
+
uses: actions/download-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: dist
|
|
54
|
+
path: dist/
|
|
55
|
+
|
|
56
|
+
- name: Publish to PyPI
|
|
57
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|