2e39dca9cf61397792af863b6a0dea6e34f60a43
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6fb0cb38a5 |
refactor(frontend): CSS pay-down, derive batch (milestone 302 step 4) — page-header, error-msg/state-msg/empty-msg, empty-title/empty-sub, required, field-hint recipes into components.css; form-buttons into rules-shared.css; scoped copies trimmed to remainders/overrides; the three views' .input becomes fs-input + width/box-sizing remainder (canon #2336)
CI & Build / TypeScript typecheck (push) Failing after 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 24s
CI & Build / Python tests (push) Successful in 1m2s
CI & Build / Build & push image (push) Skipped
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4ba544e2af |
refactor(theme): retire the --color-* shim — the sweep it promised, run
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 17s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 37s
#2533. theme.css claimed "removing this block is a rename sweep across the
components, tracked separately" — written in
|
||
|
|
bd60d679d9 |
refactor(theme): remove 184 var() fallbacks — every one was unreachable
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / integration (push) Successful in 23s
CI & Build / TypeScript typecheck (push) Successful in 25s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 43s
#2277 counted ~150 "raw colour literals bypassing the tokens". Measuring them told a different story: 184 sat in `var(--token, #fallback)` position, and a check against theme.css shows every one of those tokens IS declared. So the fallbacks could not render. Not drift — vestigial. They were also not this palette. The most common were Tailwind and Flat-UI defaults — #6366f1 indigo, #22c55e green, #f59e0b amber, #3b82f6 blue, #e74c3c and #27ae60 — a second, unsanctioned colour scheme sitting in the codebase looking like the app's colours to anyone reading it. Removing them is not tidying. #2319's lesson is that a fallback is WORSE than a missing token: a missing token renders as nothing and someone eventually notices, while a fallback renders something plausible forever. These 184 were one token rename away from silently repainting the app in Tailwind. The design token check would catch the rename — but the fallback is precisely the thing that would make it invisible if the check were ever bypassed. Literal count 152 -> 45, which matters beyond the number: a report that is mostly unreachable noise is one people stop reading, and then it stops working while still passing. What remains should be genuinely worth looking at. Done with a paren-aware transform, not a regex — `var(--x, rgba(0,0,0,.5))` nests parens and `[^)]+` would cut at the first one and leave `))` behind. Verified after: every changed line is a fallback strip and nothing else, and every var() reference still resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
1a959b1db0 |
refactor(ui): one button definition, aligned to the design system
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / integration (push) Successful in 39s
CI & Build / Python tests (push) Successful in 1m12s
CI & Build / Build & push image (push) Successful in 1m12s
`.btn-primary` was defined five times in five scoped stylesheets and all five had drifted: three paddings, three font sizes, three disabled opacities — and ProjectListView had no disabled style at all, so a disabled button there looked enabled. Nothing detected any of it. A scoped duplicate is not a rule violation, not a broken reference, and not a recorded snippet, so no existing check could see it (#2273). assets/components.css is now the single definition of the core four — primary, secondary, ghost, danger — plus the small modifier, in design-system tokens throughout. Operator's call to align to the system rather than to the majority of current values, so buttons move to the 8px radius and 12px label the system specifies, from the app's 4px/14.4px. Class names are unchanged, so there are no template edits: the existing surface is repurposed, not rebuilt. STAGING PROPERTY that makes this safe to land ahead of the rest: a Vue `<style scoped>` rule compiles to `.btn-primary[data-v-…]` (specificity 0,2,0) and beats a plain global selector (0,1,0). So the shared sheet changes nothing for a view still carrying its own copy, and every intermediate state of the remaining migration is coherent rather than half-applied. Two divergences corrected on the way, both worth naming: - SnippetEditorView's `.btn-secondary` was a GHOST in disguise — outline styling under the secondary name, while the house style says secondary is filled bronze. It now looks like what it is called. - SnippetDetailView and SnippetListView tinted a ghost button's label with the ACCENT on hover. The house style reserves the accent for identity and active state, never general chrome. DesignView reported "no shared button exists" as an honest gap and declined to draw a look-alike. That gap is closed, so it now renders the app's real classes — the specimens cannot drift from the app without drifting the app. Net -177 lines. Follows: the ~20 semantic one-offs (.btn-save, .btn-delete, .btn-toggle …) and the compound overrides in ProjectView, one of which puts the accent on a primary action button. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
4c9a637507 |
fix(theme): text on a filled colour needs its own token — the old one inverts
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 29s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 55s
CI & Build / Build & push image (push) Successful in 41s
76 hardcoded `color: #fff` now resolve to --fs-text-on-action, a new token that is parchment in BOTH modes. The design system said they should supersede to --fs-text-primary, on the recorded reasoning that "there is no 'text on action' colour, there is just the text colour." That is true on dark and wrong on light. --fs-text-primary inverts to #14171A; the surfaces underneath it do not invert at all — every one of these 76 sits on an action colour, a semantic colour, the accent, or the CTA gradient, all of which hold a single value across modes. Sweeping as recorded would have put obsidian text on moss green: roughly 2.4:1, against a house style whose stated floor is WCAG AA. It would have looked correct to me, because I checked it in the mode where it was correct. --color-accent-fg had the same defect independently and is repointed too. The token check now reports zero superseded literals, down from 30 files, and raw colour literals drop 246 -> 169. Closes #2275. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
fe63f3985b |
feat(snippets): un-merge — reverse one source out of a merged survivor
CI & Build / Python lint (push) Successful in 3s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 40s
Closes the last of milestone #232. The task said to settle the design before coding; here is what was settled and why. THE HAZARD. Restoring a merged-in source from the trash brought the record back but never stripped its locations off the survivor, so both claimed the same call sites and the reverse lookup read the duplicate claims as real. Subtracting blindly is not a fix: a location can arrive from a source AND genuinely be the survivor's own, and _normalize_locations dedups them into one, so blind subtraction would strip a call site the survivor owns. Same problem defeated partial un-merge — `merged_from` recorded ids, not which locations came from which source. THE ANSWER. Record per-source attribution AT MERGE TIME, where it is known exactly: each entry keeps only what that source ADDED, computed incrementally as sources fold in. Anything the survivor already had, or an earlier source already brought, is attributed to nobody. Both open questions fall out of that one change — partial un-merge is exact, and a survivor-owned location can never be stripped, because it was never attributed in the first place. The shape moved from [id] to [{id, locations, tags}]. Free to do: the corpus holds one snippet and zero merges, so there is no legacy data (rule #22). A bare int still normalizes to {"id": n} — not legacy tolerance, but because snippet_fields falls back to PARSING THE BODY when a row has no `data`, and the body's provenance line can only carry ids. Such an entry shows history and refuses un-merge with a reason rather than guessing. WHICH SURFACE. Neither option in the task, quite. Making trash-restore notice the merge would teach the generic trash path snippet semantics for one record type. Instead un-merge OWNS the restore: one operation, one authorization check, trash stays ignorant. Restoring by hand is still allowed and still leaves both records claiming the same places — so un-merge treats an already-alive source as the normal case and goes straight to the subtraction that repairs it. That is the state that motivated the feature, not an error. Adds trash.restore_entity(user_id, type, id) — the missing inverse of delete(), which returns a batch id callers don't keep. Restores the whole batch, since the batch is the entity plus its cascade. Refs #2165 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
0d396de215 |
feat(snippets): record merge provenance on the survivor
CI & Build / Python lint (push) Successful in 4s
CI & Build / integration (push) Successful in 38s
CI & Build / TypeScript typecheck (push) Successful in 40s
CI & Build / Python tests (push) Successful in 59s
CI & Build / Build & push image (push) Successful in 1m7s
Merge kept the target's fields, unioned locations and tags, and trashed the sources — recording nothing about what it absorbed. If a variant handled an edge case the survivor doesn't, that difference left the visible record entirely; recovering it meant knowing to go digging in the trash. The survivor now carries `merged_from`: a "**Merged from:** #2, #3" line in the body for humans, and the same list in the `data` mirror for queries, written from one value like every other field (#2087). It accumulates rather than replaces — a target merged twice keeps both histories — and skipped sources (cross-owner, per #231) are excluded, so the record never claims to contain something it never absorbed. Ordinary edits carry it forward. update_snippet recomposes body and mirror from scratch, so an omission there would silently erase the history on the next unrelated edit; that path is pinned by its own test, including the pre-0070 case where the body line is the only copy. Surfaced in the snippet detail view as a "Merged from" row — the view renders parsed fields, not the raw body, so the body line alone would have been invisible to the operator (rule #27). Un-merge, the other half of #2087, stays open: restoring a source from trash still doesn't strip its locations off the survivor, and what partial un-merge should mean is a design question, not a coding one. `merged_from` is the record that makes it tractable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLwAaV4DQEmVyn496HnEvt |
||
|
|
04b58ce01e |
feat(acl): shared records are search-only and always labelled as someone else's
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 28s
CI & Build / integration (push) Successful in 35s
CI & Build / Python tests (push) Failing after 41s
CI & Build / Build & push image (push) Has been skipped
Narrows the |
||
|
|
7a81b7333e |
feat(scribe): snippet merge UI — multi-select merge, repeatable locations
CI & Build / Python lint (push) Successful in 4s
CI & Build / integration (push) Successful in 29s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 53s
CI & Build / Build & push image (push) Successful in 1m2s
Step 3 of the snippet-merge milestone (#231): the human surfaces for merge + multi-location, at v1 quality. Frontend: - SnippetListView: a Select mode (checkbox on each card) → a sticky action bar → a merge modal that lets you pick which selected snippet is the canonical (the others fold in and go to trash). Accent border on selected cards, Moss action buttons (Hybrid rule). - SnippetEditorView: the single Location fieldset becomes a repeatable locations list (add/remove rows), so editing a merged snippet no longer collapses its call sites — no data loss. Sends `locations`. - SnippetDetailView: renders every location (Location vs Locations label). - api/snippets.ts: SnippetLocation type, `locations` on fields/input, mergeSnippets(). Backend (editor enablement): - create_snippet service + POST route accept an optional `locations` list; PATCH route forwards `locations` — so the editor's location list works uniformly on create and edit. Single repo/path/symbol remain the one-location shorthand (MCP create contract unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t |
||
|
|
d257c0fd67 |
feat(scribe): snippet management UI + REST routes; embed snippets on create
CI & Build / Python lint (push) Successful in 3s
CI & Build / integration (push) Successful in 23s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 1m4s
Step 6 of the Drafter recall milestone (#227): a human-facing surface for the reusable-code snippets that agents record via MCP, plus the REST API behind it. Backend embeds snippets inline on create/update so they're recallable immediately, not only after a restart. Backend: - routes/snippets.py: GET/POST /api/snippets, GET/PATCH/DELETE /api/snippets/<id>. Share-aware per rule #78 (get_note_for_user + can_write_note), writes performed as the owner; list owner-scoped — mirrors routes/notes.py. Registered in app.py. - services/snippets.py: embed on create/update via a _embed_snippet fire-and-forget helper, covering BOTH the MCP tool and the REST route by construction. A snippet's value is immediate recall, so it can't wait for the startup-only backfill (see issue: MCP create path doesn't embed inline for notes/tasks generally). - tests/test_routes_snippets.py: structural registration + handler/service contract + PATCH-field ↔ update_snippet-kwarg parity (rule #33). Frontend (Vue 3 + TS): - api/snippets.ts: typed client, modeled on api/systems.ts. - views: SnippetListView (search, skeleton/empty/error states), SnippetDetailView (read + copy-to-clipboard, ConfirmDialog delete), SnippetEditorView (create/edit all fields, Ctrl/Cmd+S, Esc, autofocus, validation). v1 quality per rules #24/#27. - router: /snippets, /snippets/new, /snippets/:id, /snippets/:id/edit. - NoteType union widened to include 'snippet'; Snippets nav link added to AppHeader (desktop pill bar + mobile menu). - Design system: Moss --color-action-primary for action buttons, accent --color-primary reserved for tags/brand (Hybrid rule); focus rings; JetBrains Mono for code/name/signature/location. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t |