fe18aaa9566c854ee767f2f2d6486a5d7ee29e61
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fe18aaa956 |
The contrast pass, and the invisible chip it found
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 18s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m53s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m19s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Android / Kotlin + Rust (APK) (push) Successful in 7m30s
Step 4 of milestone 309. All 40 combinations measured rather than eyeballed — 10 hues x 2 themes x 2 weights, dark ones composited over the board the way Compose and CSS both do, against the text actually drawn on a card (neutral-700/300 body, neutral-500/400 meta). Body text ranges 8.23:1 to 13.01:1 against a 4.5:1 requirement; meta text 4.33 to 7.11 against 3.0. Every combination passes AA with room to spare, so the two ramps step 3 introduced need no adjustment. That is the boring half. THE PASS FOUND A REAL REGRESSION. A tagged note takes its first tag's colour and is drawn at that hue's `-100` — which is exactly what the chip uses as its fill. Measured contrast between the chip and the card it had itself coloured: 1.00 in light mode. Perfectly invisible. Dark was 1.04-1.07, invisible in practice. On every tagged note the tag name had stopped reading as a chip and become loose text, and nothing about step 3 looked wrong while writing it. Fixed with an EDGE rather than a different fill. A fill can collide with any card colour and chasing that would need the chip to know what it is sitting on; a border in the chip's own foreground reads against any background and needs no plumbing. Alpha is 0.60, measured: 2.32:1 at worst, where the 0.30 I first wrote gave 1.49 and was no edge at all. It does not reach WCAG 1.4.11's 3:1, which needs 0.80 and draws a hard outline instead of a hairline. 1.4.11 governs boundaries carrying REQUIRED information, and a chip's information is its text — passing AA at 8:1 or better on every card here. The number and the reasoning are both in the source so the judgment can be overruled rather than rediscovered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
988e1d3f00 |
A note's colour is its first tag's colour, at a heavier weight
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 20s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m41s
Android / Kotlin + Rust (APK) (push) Failing after 3m53s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m7s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Four #todo notes on the operator's board in four different colours, because the tint was derived per-note-id and ignored tags entirely. Now a tagged note wears its first tag's colour, so notes that share a tag share a look. TWO WEIGHTS, NOT ONE RAMP. The operator, seeing step 1: "the tints look the same as the chosen colors". They did — there was only one ramp. `strong` is not a second decision, it IS whether the colour was chosen: a tag (or, until step 5, the picker) means somebody said what this note is, while a derived tint only means the board should not be a wall of white. The two weights move in OPPOSITE directions per theme, because that is where each has headroom. The operator asked whether the tint could go lighter instead of the tagged end going darker; in dark mode that is the better half of the answer, so the derived end drops to a quarter opacity — closer to the board, which gives the light body text MORE contrast rather than less. Light mode has nowhere to go below `-50` without being white again, so there the gap opens by deepening the chosen end to `-100`. No hex was transcribed for any of it. `-100` is already in NoteTint.kt as every hue's `lightChipBackground`, and the dark weights are the existing `-950` fill re-alphaed, so the only two numbers that have to agree by hand are the alphas. Copying ten more Tailwind values from memory is exactly how this mirror would have drifted. `default` is marked not tintable — it is the ABSENCE of a colour, there is no emphatic version of it, and re-alphaing its opaque neutral fill would have made every draft card translucent. Borders untouched: the fill is the signal, moving both muddies the edge. Resolution order is explicit pick, then first tag, then the id hash. First tag because it is the one you control by typing; manual labels count the same as #tags because nobody can tell which kind they made by looking. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6fbee27f9c |
A tag with no colour of its own derives one from its name
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 15s
CI & Build / integration (push) Successful in 18s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Update manifest (push) Successful in 6s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m52s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m10s
Android / Kotlin + Rust (APK) (push) Successful in 7m43s
Every #tag ever typed is `default`. `notes/tags.py` mints one as `Label(owner_id=…, name=name)` with no colour, so it takes the column default — which means tag-driven note colour, built on top, would have left the board exactly as grey as it was. Four #todo notes in the operator's screenshot, four different colours, because the tint is per-note-id and ignores tags entirely. DERIVED RATHER THAN PERSISTED AT MINT TIME, reversing the plan in 2965. That plan wanted a hashed colour written wherever a label is born, and named the risk in its own body: `find_or_create_label` is "easy to miss, and it is the common one", because most tags are born from typing `#grocery`, not from a management screen. Deriving has no mint points to miss, needs no backfill for the tags that already exist, and reuses the hash and the fixture the notes already have. The cost is that renaming a tag recolours it. That is defensible — the name IS the tag — and an explicitly picked colour is still stored and still wins, so tag colours stay editable exactly as asked. Lowercased before hashing: tags dedupe case-insensitively, so #Todo and #todo are one tag and must not be two colours. All five places a label's colour is drawn now resolve the same way — the card chip, the editor chip, the drawer's tag list, and the management modal's dot and swatch ring. The modal's ring follows the resolved colour rather than the stored one, so opening the picker highlights what you can already see instead of nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6f173b166b |
Every note carries a tint, derived from its id
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / Python tests (push) Successful in 9s
CI & Build / integration (push) Successful in 15s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m33s
Android / Kotlin + Rust (APK) (push) Failing after 6m21s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 7m2s
Desktop (Tauri) / Update manifest (push) Successful in 3s
The board was a wall of white rectangles: `default` is the colour nobody picks, so it was the colour of every note except the two the operator had coloured by hand. Reported twice — 2026-08-23 as "a wall of broken up text", and again today as "all the existing notes are the same dull color". The ask was "random subdued colors", but random is the one thing it must not be. A tint rolled at render time would differ between the phone and the browser and change on every reload. FNV-1a over the note's id is deterministic, identical on every surface, needs no column and no migration, and a note keeps its colour for life — which is what "random" meant here. Two implementations, deliberately mirrored, same discipline as the checklist grammar. The Kotlin half lives in a Compose-free file so a host-JVM test can pin the fixture; the TypeScript half carries the same four ids and hashes as a comment because the frontend has no test runner at all — its whole CI lane is `vue-tsc --noEmit`. That asymmetry is worth naming rather than papering over. A draft has no id yet (DRAFT_ID is ""), so it stays white until it is saved. Hashing the empty string would give every draft one shared tint and then change it on save anyway — two surprises where one will do. An explicitly-picked colour still wins. The picker is on its way out (milestone 309 step 5) but it has not gone yet, and a hand-coloured note changing under the operator would read as data loss. First of five steps toward colour coming from tags. This one stands alone: no storage change, nothing removed, and the board stops being white today. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
894dd4ac42 |
graph: interactive liveliness (drag/pan/zoom, unlinked toggle, label colors)
Backend /api/graph now returns ALL non-trashed notes as nodes, each with its first label's color; edges unchanged. GraphView reworked: drag a node to reposition it (pins to cursor + reheats the sim), pan the background, wheel- zoom toward the cursor, a "Show unlinked notes" toggle (connected-only by default), a Reset view button, and nodes filled by label color. Click (a press without a drag) still opens the note; re-fetches on view open. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm |
||
|
|
f321c2bf70 |
labels: per-label color
Give labels a color (migration 0011 adds labels.color, server_default 'default' so existing labels keep the neutral chip). The PATCH endpoint now updates name and/or color; note serialization carries each label's color. Frontend: a swatch picker per label in the Edit-labels modal, colored chips on cards + in the editor (LABEL_CHIP_CLASSES), and a color dot on each sidebar label. Reuses the note color vocabulary. (Graph node coloring rides this in the graph-liveliness task.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm |
||
|
|
0f604f9a26 |
M1: masonry board UI — quick-add, note cards, editor, views
- notes Pinia store (load/create/pin/archive/color/trash/restore/delete) with view-aware reconcile; api client patch/del methods. - colors.ts palette (10 keys → light/dark card + swatch tints). - QuickAdd (collapsed → expand, title/body/color, click-outside/Esc to save), NoteCard (color tint, click-to-edit, hover action bar), NoteEditor modal, ColorPicker, inline Icon set (no icon dep). - BoardView rewrite: Notes/Archive/Trash routes, CSS-columns masonry, Pinned + Others sections, per-view empty states, sign-out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm |