f0ce5687cc972b4c5eb638350cf5bbbeffad53cc
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5989ffc1c6 |
desktop: Import and Export work offline; the menu toggle is reachable; errors say why
CI & Build / Build now, or wait for Android? (push) Successful in 4s
Android / Build, or is the channel already serving this? (push) Successful in 4s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 12s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Python tests (push) Successful in 13s
CI & Build / integration (push) Successful in 48s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 4m26s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m10s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m32s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 12m42s
Export and Import were a link to the server and a reject("needs a server") on the
desktop. Both now run in the core with no server:
- core/src/local/portable.rs builds the same zip the server writes (notes.json,
a Markdown file per note, each attachment this device holds) and reads either
export marker or a Google Keep Takeout zip, with the server's decompression
budget and an all-or-nothing transaction. Export saves to Downloads (no new
plugin) and the sidebar says where; Import takes the archive as raw IPC bytes.
- core/testdata/portable.json pins the format for both copies: the server runs
its Keep and native readers against it (test_portable_fixture.py) and checks
its real export's keys (test_integration.py); the core runs the same cases.
- Found on the way: both importers skipped a Keep note that is only a photo as
"empty". It now imports, on the server and in the core.
- New dependency, approved: `zip` (deflate only) plus `flate2` on its pure-Rust
backend, both already in the lockfile.
The AppImage applications-menu toggle moves from Account, which the desktop
never shows, to the Sync page; the first-run prompt now says so.
errorMessage (#5236) replaces the hand-rolled `.error ?? …` / `.message ?? e`
reads at the remaining catch sites, so a desktop failure shows its real reason.
Task #5170.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
c5f93cf9f1 |
rename: the sign-in screen shows Inkwell's mark, and every tab says Inkwell
Android / Build, or is the channel already serving this? (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Skipped
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python tests (push) Successful in 12s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / integration (push) Successful in 43s
CI & Build / TypeScript typecheck (push) Successful in 12s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build & push image (push) Successful in 1m0s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m28s
Desktop (Tauri) / Update manifest (push) Successful in 10s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 6m22s
The login and register screens still drew a hard-coded "TS" tile. They now use /icon.svg, the same mark the shell's header shows. Browser tabs took index.html's static <title> and never changed it, so every tab read the same, and some browsers showed the URL instead. usePageTitle, mounted once in App.vue, sets "<page> · <site name>". Routes outside the shell name themselves with meta.title. Board lenses use the lens name the header already shows, now in useLensName so the tab and the header read from one place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bc22f8e249 |
Remove [[wiki-links]], backlinks and the graph
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 31s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Failing after 37s
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Failing after 6s
CI & Build / Build & push image (push) Skipped
CI & Build / Python tests (push) Successful in 8s
Android / Kotlin + Rust (APK) (push) Failing after 1m56s
Operator, 2026-08-22 (note 2897): ThoughtSync is an intermediary surface. You
write here because it's easy — a notebook in your pocket — and later you recall
the thing and go finish it somewhere else. Recall is the product; organization
is secondary. A linking system is organization, and it isn't what this is for.
So: `[[wiki-links]]`, backlinks, the `[[` autocomplete, the note_links table,
`/api/notes/link-search`, `/api/notes/<id>/backlinks`, the whole graph blueprint
and GraphView. Rust core loses `extract_links`, `backlinks`, `link_search` and
`create_titled`; the desktop loses the three Tauri commands that exposed them.
This subsumes
|
||
|
|
18a58fb5da |
frontend: the board glides and the editor grows from its card (task 1914)
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Python tests (push) Successful in 10s
CI & Build / Build & push image (push) Successful in 1m0s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m30s
Android (Tauri) / Android APK (debug) (push) Successful in 4m25s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 6m8s
Two of M7's motion targets. prefers-reduced-motion was already in place from the 1999 pass and gates both of these for free. FILTERED REFLOW. The three card grids become TransitionGroups sharing one transition name, so "how the board moves" is defined once in CSS rather than three times in markup. Vue's TransitionGroup does the FLIP itself — measure before, measure after, transition the difference away — so no animation dependency, which the task called for. Leavers are deliberately NOT pulled out of flow with position:absolute, the usual TransitionGroup trick. This masonry is CSS multi-column, and an absolutely positioned child escapes its column to the container's origin: a note would fly diagonally across the board on its way out. Keeping leavers in flow costs a small settle when the element is finally removed, so the leave is the shortest of the three durations. EDITOR CONTINUITY. useNoteEditor.open() is the one place that knows which card was clicked, so that is where the card's on-screen centre is captured; the editor panel then scales from that point. Deliberately not a true shared-element morph: scaling by the real card-to-panel ratio distorts the text on the way, and a card is often a third of the modal, so an honest ratio reads as a zoom rather than a transition. The task sanctioned a good-enough scale/position tween; this is that. A point rather than a rect, because nothing needs the card's size and a point survives the card being filtered away while the editor is open. Consumed on read, so a compose — which has no card — cannot inherit the origin of whatever was edited before it and grow from an arbitrary corner. The animation lives inside NoteEditor rather than in the five views that render it: the leave has to finish BEFORE the host unmounts, so the component owns its own visibility and tells the host when it is done. visible starts true with `appear`, because the panel lives inside that v-if and would not exist to measure otherwise. The origin is measured with offsetLeft/offsetTop rather than getBoundingClientRect — enter-from has already applied scale(0.94) by then, so the bounding rect is of the shrunken panel and the origin would land off by a few pixels. Offsets are layout geometry and ignore transforms. Durations are 140-220ms. The brief is continuity, so a card should read as having moved, not as having performed. NOT verified: motion is a visual property and there is no frontend test lane, no device, and no app run here. vue-tsc proves it compiles. Whether it FEELS right is an operator live pass, which is what M7's own verification section asks for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
be0eb94225 |
frontend: reorder cards with Pointer Events so touch can do it at all (task 2697)
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 9s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build & push image (push) Successful in 36s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m17s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m12s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Native HTML5 drag-and-drop never fires from touch — the API predates it and was never wired to it — so on a phone reordering did nothing whatsoever, and the grip that starts it was hover-gated on top of that. Pointer Events cover mouse, touch and stylus on one code path instead of two. The awkward part is hit-testing. Native DnD routed dragover/drop to whatever was under the cursor, so each card learned on its own that it was the target. A captured pointer sends every move to the element that captured it, so the dragged card has to hit-test for itself and publish the result where the other cards can see it — hence the shared refs in useCardDrag. It reads the DOM via elementFromPoint rather than tracking geometry because the board is a CSS masonry: visual order isn't derivable from model order, and cards reflow as the column count changes. Asking the browser what is actually under the finger is the only answer that stays true. Capture is what makes the gesture survive crossing a card boundary; touch-action: none claims it from the browser's scrolling; a 6px threshold keeps a tap from becoming a drag; and pointercancel is handled so a system interruption leaves no half-set state. The parent contract is unchanged apart from `drop` now carrying the target's ID rather than its note — the dragged card finds its target in the DOM, so an id is all it can know without a second lookup. BoardView keeps its own tracking of what was picked up; that it now duplicates the composable's draggingId is real, and noted for the DRY pass rather than expanded into here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3a1496e5fa |
frontend: honor prefers-reduced-motion, and let frontend work reach the desktop
CI & Build / Python lint (push) Successful in 3s
CI & Build / Python tests (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Build & push image (push) Successful in 31s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m38s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m33s
Two halves of the same gap. The app had no reduced-motion handling at all — the setting appeared nowhere in the frontend — and the desktop build didn't rebuild on frontend changes, so shared UI work shipped to the web and silently never reached the desktop app. The CSS guard is global and blunt so it catches every Tailwind `transition` already scattered through the components, and catches M7's motion work without each new component having to remember. Near-zero durations rather than `none`, so transitionend/animationend still fire and nothing waiting on them hangs. useReducedMotion covers what CSS can't reach: JS-driven motion, where the honest response to the preference is no animation at all rather than a faster one. It's reactive because the setting can change while the app is open. The path filter was narrowed to the adapter/bridge directories against a "~20-40 min" build cost recorded in the header. Measured runs are 4-5 minutes, so that cost isn't there, and the frontend is compiled into the binary by generate_context! — any part of it changing means the shipped desktop app is stale. Desktop, web and Android are peer surfaces on one quality bar, so shared frontend work has to reach all of them by construction rather than by whichever directory it happened to touch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
590d3ff2f6 |
M9 S3 (frontend): recall views adopt useNoteList + AsyncState/EmptyState
The Search / Timeline / Reminders views each hand-rolled the same items+loading+error scaffold, retry button, and editor-host glue. - useNoteList(fetcher, fallbackError) (new): the load-a-note-list scaffold (items/loading/error + a load() that never leaves a half-state). Views supply just the fetcher; the refs drive <AsyncState>. - SearchView / TimelineView / RemindersView: adopt useNoteList + useNoteEditor + <AsyncState>/<EmptyState>; drop the local list/loading/error refs, the duplicated retry blocks, and the notes.items-shadowing navigate glue. - GraphView: editor host now via useNoteEditor (openNode/closeEditor/onNavigate collapse to navigate); loading/error via <AsyncState>. Its two empty states keep inline markup/buttons, so they stay custom (not forced into EmptyState). - reminders store: fetchReminders() is the single owner of /api/notes/reminders; both the background poll (check) and RemindersView read through it. Frontend-only; CI vue-tsc is the type gate (no local typecheck, rule 10). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm |
||
|
|
18ca4d4db4 |
S1: frontend infra — AsyncState/EmptyState + useNoteEditor, adopted in BoardView
M9 section S1, commit 4 (frontend). Introduce the shared UI primitives the 7 data views + 5 editor hosts were hand-rolling: - components/AsyncState.vue: the loading / error(+Retry) wrapper (emits `retry`). - components/EmptyState.vue: the centered title/subtitle "nothing here" block. - composables/useNoteEditor.ts: the editing/open/close/navigate glue every editor host duplicated, as one controller (onClose hook + local-list resolution for [[wiki-link]] navigation). BoardView adopts all three: its three hand-rolled loading/error/empty blocks collapse into <AsyncState> + <EmptyState>, and its editor glue into useNoteEditor. The other views + editor hosts adopt these in the Organize (S3) and Auth (S5) sections. Behavior-preserving. Frontend has no local typecheck (rule 10); CI's vue-tsc is the gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm |