Commit Graph
471 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5.5 962637ea8b TEMPORARY: force the APK job red to watch the image gate reject (#5419)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 11s
Android / Core and FFI clippy and tests (push) Successful in 49s
Android / Kotlin + Rust (APK) (push) Failing after 37s
Android / Build the server image (push) Skipped
CI & Build / integration (push) Successful in 1m39s
CI & Build / Build & push image (push) Skipped
Reverted by the next commit. Expect: build fails, server-image skipped,
no ci.yml dispatch, :dev unmoved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 16:51:54 -04:00
bvandeusenandClaude Opus 5.5 b2d50009d3 android.yml: a red Android lane no longer publishes the server image (#5419)
server-image ran under always(), so a failed core check or APK build still
dispatched ci.yml. Run 8839 published :<sha> for 0ecdbfe while its APK lane
(8835) was red. Rule 177: nothing publishes on red. With always() dropped,
the dispatch needs rust and build to succeed. A skipped decide still skips
it, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 16:51:34 -04:00
bvandeusenandClaude Opus 5.5 76fe96d135 MainActivity imports OnEachForeground (#5372)
Android / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Python tests (push) Successful in 10s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 8s
Android / Core and FFI clippy and tests (push) Successful in 58s
CI & Build / integration (push) Successful in 1m59s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m29s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m4s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m50s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m32s
Android / Build the server image (push) Successful in 0s
fb95c9b moved the foreground latch into ui/ForegroundTransitions.kt, but
MainActivity lives one package up and imports what it uses from ui by name;
run 8835's compileDebugKotlin stopped on the two unresolved calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 16:05:41 -04:00
bvandeusenandClaude Opus 5.5 ef22910154 DRY pass #3: the labels store swaps a returned label in one way (#5373)
rename and setColor each found the label and replaced it, keeping the count
the single-label PATCH doesn't recompute; keepingCount(updated) is that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:39:25 -04:00
bvandeusenandClaude Opus 5.5 cd8df384a4 DRY pass #3: one compact dialog button (#5373)
ShareDialog's Share and NoteEditor's Add note / Close each spelled out the
same compact button, smaller than .btn. .btn-compact is its shape and
.btn-compact-primary its brand fill; Close keeps its neutral colours beside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:38:36 -04:00
bvandeusenandClaude Opus 5.5 0ecdbfe257 desktop.yml: every job has its own deadline (#5409)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 10s
CI & Build / Python tests (push) Successful in 12s
Android / Core and FFI clippy and tests (push) Successful in 31s
CI & Build / integration (push) Successful in 1m34s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 1m56s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m10s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m54s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Failing after 5m46s
Android / Build the server image (push) Successful in 1s
Run 8830's Linux bundle sat on a rust-lld link whose 17 threads were all
parked on futexes until the runner's own 30-minute cap ended it. verify, the
Linux and Windows builds get 20 minutes (warm runs take ~4, and a cold one
compiles the tree in about two), the manifest 5. Kept under the runner's cap
so these are the limits that fire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:37:34 -04:00
bvandeusenandClaude Opus 5.5 78810d8832 DRY pass #3: core tests open their store and seed a label one way (#5373)
Five test modules wrote the same db() over memory_conn, and portable.rs
unwrapped it inline; local::test_db() is that. Four pull/push tests wrote the
same label INSERT; local::seed_label(conn, id, name, dirty) is it. Both are
cfg(test), beside memory_conn, as wire::sample_note is beside wire::Note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:17:30 -04:00
bvandeusenandClaude Opus 5.5 1b128a9374 DRY pass #3: a secret setting is the text input with a password type (#5373)
SettingsView drew secret and plain text settings as two inputs differing only
in type, autocomplete and the saved-value placeholder; one input binds those.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:16:16 -04:00
bvandeusenandClaude Opus 5.5 9dcdb43568 DRY pass #3: both sync work requests take one network-and-backoff policy (#5373)
enable and pushSoon each set the CONNECTED constraint and the 30-second
exponential backoff; B.online() on WorkRequest.Builder sets both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:16:04 -04:00
bvandeusenandClaude Opus 5.5 91ec677025 DRY pass #3: checklist near-misses live in the grammar fixture (#5373)
test_notes.py and derive.rs each kept the same six lines of prose that look
like a checklist item. Four were already task_lines cases in grammar.json;
the other two (empty brackets, no bullet) join them, so the web suite now
tests them too, and both hand lists go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:15:17 -04:00
bvandeusenandClaude Opus 5.5 0e21b8e7f1 DRY pass #3: SyncView's update choices are one fieldset, twice (#5373)
The source and channel fieldsets were the same markup over different options.
updateChoices holds each set with its legend, radio name and when it is
offered, and one v-for draws them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:14:10 -04:00
bvandeusenandClaude Opus 5.5 31d8b1e65b DRY pass #3: unlinking a device by id or by its own token is one tail (#5373)
revoke_self and revoke_device each loaded the caller's DeviceToken, deleted
it, committed and audited DEVICE_UNLINKED; _unlink_device(which) is that,
owner-scoped as before, with each route keeping only how it names the row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:13:39 -04:00
bvandeusenandClaude Opus 5.5 498b23789c DRY pass #3: one one-time secret reveal (#5373)
AccountView's device-token reveal was OneTimeLink's markup and copy handler
again. OneTimeLink becomes OneTimeSecret (value, note, noun = link | token),
and the account page shows a new token through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:13:18 -04:00
bvandeusenandClaude Opus 5.5 a0249564e2 DRY pass #3: the web asks one question about a password's length (#5373)
RegisterView and ResetPasswordView each wrote the min-length check and its
message; config.passwordRefusal(password) is that check, the web's side of
auth._password_refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:12:32 -04:00
bvandeusenandClaude Opus 5.5 8c45725a2a DRY pass #2, batch 8, F17: a person and an account have one shape each (#5372)
Web: GroupList's Person was adapters/repo's Member field for field, and
AccountList's Account was the session store's User; both now import them.
The bridge's Identity stays, as the Tauri mirror of the core's struct.

Server: auth._serialize_user and accounts' _serialize_account wrote the same
four fields. serialize.serialize_user is serialize_person plus is_admin, and
the account list adds created_at to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:11:29 -04:00
bvandeusenandClaude Opus 5.5 0d82e26eea DRY pass #2, batch 8, F20: drawer titles and tag colour names come from strings.xml (#5372)
Destination carried its English title as a field, and NoteTint its colour
name; every other word on screen is a string resource. Destination.title()
now reads nav_notes/reminders/archive/trash (a tag's destination keeps its
name), and NoteTint.label is a @StringRes. Error fallbacks raised in view
models and the install receiver stay as constants: they are made outside
composition, with no Context to read a resource from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:10:15 -04:00
bvandeusenandClaude Opus 5.5 cba428461c DRY pass #2, batch 8: the Shared-with-me facet is tested once (#5372)
sharing.test.ts round-tripped shared=with_me, which facets.test.ts already
does with every facet; its unknown-value case moves there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:08:38 -04:00
bvandeusenandClaude Opus 5.5 45c286a71f DRY pass #2, batch 8: inkwell.notes stops re-exporting its siblings (#5372)
notes/__init__ re-exported 17 names so it could stand in for the old single
module. Only sync.py and test_notes.py used that, and each now imports from
the module that defines the name; __all__ and the nine imports __init__ held
only for it go. sync.py also drops sa_delete, func, live and NoteLabel,
unused since 8eff5f6 (#5382). The integration test's patch on
inkwell.notes.schedule_unfurls stays: the routes call it from there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:08:24 -04:00
bvandeusenandClaude Opus 5.5 7392ca2e97 DRY pass #2, batch 8, F20: takeShared's two SEND branches are one (#5372)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:07:24 -04:00
bvandeusenandClaude Opus 5.5 fb95c9b66f DRY pass #2, batch 8, F20: one foreground observer, one foreground latch (#5372)
FlushOnStop was ForegroundTransitions with only its ON_STOP half, so it goes
and the editor calls ForegroundTransitions(onBackground = flush); both halves
now default to nothing, dropping three onBackground = {}. AutomaticUpdate and
AutomaticSync each kept a wanted flag set on the way in and consumed in a
LaunchedEffect once ready; that latch is OnEachForeground(ready, onBackground,
act), with each caller's ready and its reason kept where they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:07:02 -04:00
bvandeusenandClaude Opus 5.5 63abff8681 DRY pass #2, batch 8: the Rust and ffi docs (#5372)
sync/mod.rs listed 5 of its 9 modules; migrate's doc sat above the v9 SQL;
client.rs had items after its test module; the ffi's sync_now doc had fused
into client_update's; complete_reminder (ffi and EditorAction) still said
recurrence advancement was to come, though the core does it; NoteQuery.view
listed views the core never matched and claimed it validated. Test scratch
dirs drop the old ts-/iw- prefixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:05:43 -04:00
bvandeusenandClaude Opus 5.5 2ce68f578a DRY pass #2, batch 8: stale comments (#5372)
useNoteList no longer names a Search view; the desktop adapter's M10.7 plan
gave way to sync under the local core; local.ts's sharing comment sat above
settings; AccountList and password_resets still said there was no mail path;
NoteEditor kept an orphan checklist-flag comment, a textarea comment from
before blocks, and the link-preview comment above the file picker; the
serialize docstring's growth plan is now what it holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:03:50 -04:00
bvandeusenandClaude Opus 5.5 e911f1d757 DRY pass #2, batch 8: the web's leftovers (#5372)
- notes/datetime.ts formatLocalDay had no caller; removed.
- style.css set body in two consecutive blocks; they are one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:01:13 -04:00
bvandeusenandClaude Opus 5.5 fae03c7eca install.sh reads INKWELL_SERVER, INKWELL_CHANNEL and INKWELL_TOKEN (#5372)
The installer's environment variables kept the ThoughtSync-era TS_ prefix
after the rename to Inkwell. They are now INKWELL_*, as is the
INKWELL_SERVER_DEFAULT line the server fills in when it serves the script.
The old names are not read any more; the operator approved the clean cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:01:13 -04:00
bvandeusenandClaude Opus 5.5 027ad6f672 DRY pass #2, batch 7, F21: update.rs reads installer markers one way (#5372)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 10s
CI & Build / Python tests (push) Successful in 12s
Android / Core and FFI clippy and tests (push) Successful in 55s
CI & Build / integration (push) Successful in 1m45s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m17s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m55s
Android / Kotlin + Rust (APK) (push) Successful in 8m19s
Android / Build the server image (push) Successful in 1s
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 30m2s
Desktop (Tauri) / Update manifest (push) Skipped
update.rs read_marker(): read the installer's marker file, parse it, and
log one that doesn't parse. adopt_installer_channel and
adopt_installer_server each wrote that out; they already shared adopt().

normalize_server's doc now says why it stays apart from
compat::normalize_base_url: one tidies what a person types, the other
refuses anything odd in what a script wrote. The tauri.conf updater
endpoint stays: read_source already documents that it is never consulted,
and removing it is a config change CI would be the first to try.

rustfmt --check is clean in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:00:39 -04:00
bvandeusenandClaude Opus 5.5 17076d6a79 DRY pass #2, batch 7, F21: the desktop's main window, clock and launcher dir (#5372)
- lib.rs MAIN_WINDOW: the "main" window label that the reminder worker,
  the capture window and the shell each wrote, 5 sites in all.
- lib.rs now_ms(): the epoch-milliseconds clock that the reminder worker
  and autosync's cycle stamp each computed. LastCycle.at_ms becomes i64,
  the same number on the wire.
- integration.rs applications_dir(): the XDG launcher directory that the
  entry path and the desktop-database refresh each built.

rustfmt --check is clean in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:00:03 -04:00
bvandeusenandClaude Opus 5.5 00dc8840cc DRY pass #2, batch 6, F20: log tags, snooze lengths, one instant parser (#5372)
- LogTag.kt: the four tags the app logs under. Eight files each declared
  one of them as a string.
- SNOOZE_HOUR/SNOOZE_DAY sit beside EditorAction.SnoozeReminder; the
  notification's snooze uses SNOOZE_HOUR instead of its own 60.
- Reminders.at reads through ui.epochMillis, the parse the card and the
  overdue check already use.

Checked with ktlint and detekt in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:58:41 -04:00
bvandeusenandClaude Opus 5.5 f46dd7a707 DRY pass #2, batch 6, F19: Banner, the tinted strip (#5372)
ErrorBanner.kt Banner(tintKey) { … } is the rounded, palette-tinted row
that ErrorBanner (red) and UpdateBanner (blue) each built. TintChip moves
to Chips.kt with CHIP_RADIUS, which keeps NoteCard.kt under detekt's
function count. Checked with ktlint and detekt in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:57:25 -04:00
bvandeusenandClaude Opus 5.5 7ee4fed246 Android: loneUrl trims without a spread
detekt's SpreadOperator flagged trimEnd(*TRAILING_PUNCTUATION) from
1c4bf56. trimEnd { it in TRAILING_PUNCTUATION } trims the same characters
without copying the array. Checked with detekt in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:57:25 -04:00
bvandeusenandClaude Opus 5.5 9b8c9474bc DRY pass #2, batch 6, F19: TitleAndBody, TintChip and Swatch (#5372)
- BoardScreen TitleAndBody: the title plus quieter body that the empty
  board and the store-unavailable screen each wrote.
- NoteCard TintChip: the one-line palette chip that the reminder and
  shared-by chips each drew in full.
- TagsScreen Swatch: the colour dot that the tag row (tappable) and the
  colour picker each built.

Kept: the label chips. Each uses its tag's ink and a bigger shape, not
the card's chip pair. Checked with ktlint in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:56:18 -04:00
bvandeusenandClaude Opus 5.5 4a810df0a9 DRY pass #2, batch 6, F19: the note menu's shared rows (#5372)
TrashedNoteItems, PinItem and ArchiveItem (EditorChrome.kt) are the rows
that the editor's overflow and the board's long-press menu each built: a
trashed note's restore and delete-forever, and the pin and archive
toggles. Each menu keeps its own order and its owner-only rows. Checked
with ktlint in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:55:06 -04:00
bvandeusenandClaude Opus 5.5 0d82c17224 DRY pass #2, batch 6, F19: manualLabelIds and sharerName (#5372)
- Note.manualLabelIds (NoteAccess.kt): the tags attached by hand, which
  the label picker, the chip's remove button and the board's create-label
  each filtered out of note.labels.
- sharerName(note): who shared a note, or "Someone", which the shared-by
  line and the card's chip each spelled out.

Checked with ktlint in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:54:27 -04:00
bvandeusenandClaude Opus 5.5 709ccca64c DRY pass #2, batch 6, F19: Sheet, the bottom sheet with its title (#5372)
EditorPickers.kt Sheet(title, onDismiss, modifier, verticalArrangement):
a ModalBottomSheet holding a full-width column with the screen margin,
clear of the navigation bar, under SheetTitle. The filter, tag picker,
reminder and share sheets each built that. A site's extra (a scroll, ime
padding, bottom space) is now applied after the navigation-bar inset
rather than before it. The total inset is the same either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:53:51 -04:00
bvandeusenandClaude Opus 5.5 f2fac1674b DRY pass #2, batch 6, F19: BackButton, the arrow out of a full-screen surface (#5372)
Panel.kt BackButton(onClick, label): the IconButton + ArrowBack that the
tags, sync and editor top bars each built. Each keeps its own spoken
label. Checked with ktlint in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:52:54 -04:00
bvandeusenandClaude Opus 5.5 74ee288fd4 DRY pass #2, batch 6, F19: Hint, the quiet line under a field (#5372)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 11s
Android / Core and FFI clippy and tests (push) Successful in 34s
CI & Build / integration (push) Successful in 1m37s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m2s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m8s
Android / Kotlin + Rust (APK) (push) Failing after 5m1s
Android / Build the server image (push) Successful in 1s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m0s
Desktop (Tauri) / Update manifest (push) Successful in 3s
Panel.kt Hint(text, modifier): bodySmall in onSurfaceVariant. That is the
secondary line that the sync pairing form, the sync screen, the update card
and the share sheet each wrote as a full Text(...) at 14 sites. Sites that
add more than a modifier (the link preview's two-line clamp) stay as they
are. So does ShareSheet's own Muted, which is bodyMedium.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:51:51 -04:00
bvandeusenandClaude Opus 5.5 3d03567633 DRY pass #2, batch 6, F19: one confirm dialog, one permission vocabulary, one #name (#5372)
- Panel.kt ConfirmDialog (moved from TagsScreen, where it was private):
  the delete-forever dialog, the sync disconnect and the tag dialogs all
  ask through it now.
- strings.xml: editor_cancel and tags_cancel were both "Cancel"; they are
  one cancel string.
- NoteAccess carries the core's permission string (wire). The access
  lookup, the share sheet's choices and the board's draft read it from
  there instead of writing "owner"/"edit"/"view" again.
- hashtag(name): the #-prefixed tag name that TagsScreen, the card and the
  editor chips each wrote.

Formatted and checked with ktlint in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:51:17 -04:00
bvandeusenandClaude Opus 5.5 ef759e7d8b DRY pass #2, batch 6, F18: one failure message, one factory shape, one editor sitting (#5372)
- ui/Failure.kt: Throwable.shownAs(fallback) and FALLBACK_ERROR. The
  core's own message, else a fallback, which Share, Tags and Sync each
  defined privately and Board and Update wrote inline.
- The five view-model factories use lifecycle's viewModelFactory { initializer }
  instead of an unchecked-cast object each.
- BoardViewModel.beginSitting: the editingSession bump the four editor
  openings each spelled out.

The fallback line stays an English constant, as it was: view models hold no
Context to read strings.xml. Formatted with ktlint in the CI image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:49:44 -04:00
bvandeusenandClaude Opus 5.5 3dd47fa178 DRY pass #2, batch 5, F17: the web's smaller pairs (#5372)
- desktop/bridge.ts listen<T>(): the no-op-off-desktop event listener
  that onSynced, onCaptured and onReminderDue each wrote.
- size.ts roughSize(): one decimal below 10, none above, for the client
  download sizes (MB) and the storage line (GB).
- BaseModal's title prop draws the heading row and close button that
  LabelsModal and ShareDialog each built.
- SyncView: switchChannel and switchSource share recheck(); the four
  update-card radios render from two option lists.

Kept: the board's and the shell's is-typing guards (they ignore different
elements), the drawer's nav beside the palette's commands (one is a laid-
out list with tags between its entries, the other a command list), the
status pills (each colours differently), and plurals (the copy is the
operator's call, raised in #5371).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:48:31 -04:00
bvandeusenandClaude Opus 5.5 6e4c4930cb DRY pass #2, batch 5, F16: the admin lists load through useLoad (#5372)
Accounts, groups, invites and activity each wrote the same first load:
clear the error, try, put the server's reason or a fallback in error,
and stop loading. useLoad (composables/useAction.ts) is that, and its
load() is also the retry.

Kept: ShareDialog and Settings. Their load sets loading back to true on a
reload, which the lists never did, so moving them would change what a
retry shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:46:14 -04:00
bvandeusenandClaude Opus 5.5 52d53fb78a InviteList: load() keeps its own error line; revoke toasts
ebbe4a6 converted the wrong try block in InviteList: the edit matched from
load()'s try down to revoke()'s catch, which left load() half-converted.
load() goes back to its own try/catch/finally. revoke() uses
toastOnFailure, as intended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:45:19 -04:00
bvandeusenandClaude Opus 5.5 ebbe4a678a DRY pass #2, batch 5, F16: one way an action reports its failure (#5372)
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 4s
Android / Build, or is the channel already serving this? (push) Successful in 8s
CI & Build / Web typecheck and unit tests (push) Failing after 11s
CI & Build / Python tests (push) Successful in 19s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Failing after 29s
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
Android / Core and FFI clippy and tests (push) Successful in 44s
CI & Build / integration (push) Successful in 1m32s
CI & Build / Build & push image (push) Skipped
Android / Kotlin + Rust (APK) (push) Failing after 4m11s
Android / Build the server image (push) Successful in 1s
composables/useAction.ts: toastOnFailure runs an action and toasts the
server's reason or a fallback; useAction adds the busy flag a button
waits on; useRowAction keeps the id of the row whose action is running.
Import, export, the menu entry, the integration prompt, sign out
elsewhere, the account reset link, the group actions (whose local act()
it replaces), invite revoke and sync disconnect each wrote that
try/catch/finally out.

Kept: AccountView's device revoke. It shows a fixed message rather than
the server's reason, and moving it would change the text. ShareDialog
shows its errors inline, not in a toast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:44:43 -04:00
bvandeusenandClaude Opus 5.5 fe1b61fa4d Android: loneUrl's chain in ktlint's layout
16ab4a1's APK lane stopped at ktlint (chain-method-continuation) on the
one-line chain 1c4bf56 wrote. Reformatted with ktlint --format in the CI
image; no code change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:42:50 -04:00
bvandeusenandClaude Opus 5.5 8899bd205f DRY pass #2, batch 5, F15: the four signed-out pages share AuthLayout (#5372)
Sign in, register, forgot and reset each wrote the same page: a centred
column, the app icon, a title, a subtitle, the form and a footer link. That
is now components/AuthLayout.vue, with the title as a prop and the
subtitle, the form, the footer and anything after it as slots. The footer
links wear the new .text-link class. Markup and classes are unchanged, so
the pages render as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:42:31 -04:00
bvandeusenandClaude Opus 5.5 b481e6857c DRY pass #2, batch 5, F14: the web's repeated class lists become component classes (#5372)
style.css gains the classes the views spelled out in full: .section-label
(17 sites), .hint (20), .form-error (13), .alert-error (5), .row-card (5),
.list-empty (5), .field (5), .page-shell (3), and the small row action
.btn-sm (4) / .btn-sm-danger (3). Only exact runs moved, so nothing renders
differently; spacing a site adds beyond a run stays a utility beside it.

Kept: the Reminders and Timeline small buttons. They carry no text colour
and inherit it, so putting them on .btn-sm would recolour them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:41:34 -04:00
bvandeusenandClaude Opus 5.5 38b94bd57e DRY pass #2, batch 4, F13: the server's remaining pairs (#5372)
- common.detach: the hold-a-reference-until-done task start that mailer and
  unfurl_queue each wrote.
- common.expired_before: the retention window, now shared by trash and the
  audit log (it moves out of retention.py, which imports audit).
- responses.too_many: the 429 with Retry-After from the credential throttle
  and the client-download throttle.
- share_sync.revoke_lost: revoke whoever could see a note before and no
  longer can, after a share or a group goes.
- serialize.serialize_person: a member as the directory, a share and a
  group listing show them.
- groups_api._get_group: the path-id lookup four group routes wrote.
- settings.apply_session_ttl: the session lifetime set at boot and on save.

Kept: the attachment-id claim check (one query; each caller answers an id it
already holds differently), the strict UUID-list parses in reorder and
set_note_labels (distinct error messages), and the checklist-items loops
(one line each).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:39:38 -04:00
bvandeusenandClaude Opus 5.5 627c5e43bc DRY pass #2, batch 4, F12: one note fetch, one tag-name match, one push-landed reply (#5372)
notes.helpers._fetch_note is the parse-id, not-purged, gated select that
_get_owned, _get_visible and _get_editable each wrote out, and note_visible
is the viewer's visibility predicate that five note reads spelled in full.
labeling.named is the case-insensitive live-name match that tags, labels
(create and rename) and the sync push each wrote; how two tag names compare
is now said in one place (#5385 will change it there). sync._landed is the
flush, read-back-the-revision and reply that four push paths ended with.

The REST routes' commit-and-serialise tails stay: each is two lines, and
whether a route refreshes the row first differs by route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:37:19 -04:00
bvandeusenandClaude Opus 5.5 16ab4a13f5 DRY pass #2, batch 4, F11: one email normaliser, one 401, one password-length refusal (#5372)
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Web typecheck and unit tests (push) Successful in 12s
CI & Build / Python tests (push) Successful in 14s
Android / Core and FFI clippy and tests (push) Successful in 57s
CI & Build / integration (push) Successful in 1m38s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m13s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m4s
Android / Kotlin + Rust (APK) (push) Failing after 4m49s
Android / Build the server image (push) Successful in 1s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m50s
Desktop (Tauri) / Update manifest (push) Successful in 3s
common.normalize_email is the trim-and-lowercase that sign-up, sign-in,
reset and invite each wrote inline. auth._unauthenticated is the 401 the
six signed-out paths returned, and auth._password_refusal is the
minimum-length check that register, reset and change repeated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:34:47 -04:00
bvandeusenandClaude Opus 5.5 a34d469e1e An attachment's file leaves the disk one way: storage.unlink_media
The purge wrote out its own try/unlink/log next to unlink_media, which says the
same thing. It couldn't import it: unlink_media lived in the notes package, which
imports retention. unlink_media moves down to storage.py, the module about what
attachments occupy, and the purge, the delete route and sync all call it.

DRY pass #2, batch 4, F10 (#5372).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:33:11 -04:00
bvandeusenandClaude Opus 5.5 f5478a0ce0 The export and the Markdown frontmatter write times through common.iso
common.iso is the server's one way to put a datetime on the wire, yet the JSON
export and the importer's Markdown frontmatter wrote out the
x.isoformat() if x else None idiom it replaces, seven times. Same output.

DRY pass #2, batch 4, F10 (#5372).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:33:11 -04:00
bvandeusenandClaude Opus 5.5 ac9035d9a5 Admin routes and the SPA fallback answer errors through responses.py
responses.py is the app's one JSON error shape, yet invites, accounts, the SPA
fallback and the test-email route still built jsonify({"error": ...}) by hand,
and two parsed path ids with their own try/uuid.UUID. They now use json_error,
not_found and parse_uuid. The download limiter's 429 stays for F13, with its
Retry-After twin.

DRY pass #2, batch 4, F10 (#5372).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:33:11 -04:00