Files: names with ZWJ/ZWNJ are refused (emoji sequences, Persian words), and the upload toast gives no reason #142

Closed
opened 2026-09-25 23:17:31 +00:00 by kayg · 30 comments
Owner

Severity: medium (common emoji and Persian/Indic names cannot be stored; the UI does not say why)

Problem

calternal_fs::normalize_new_name refuses U+200B–U+200D and U+FEFF. That blocks:

  • every emoji ZWJ sequence: family 👩‍👩‍👧‍👦 flag 🏳️‍🌈.md, 👩‍💻 notes.md, ❤️‍🔥.jpg (400 on upload, mkdir and rename);
  • ZWNJ (U+200C), which Persian, Urdu and Indic writing need inside words (نامه‌ای.txt);
  • files from a phone or a Mac that already have such names (sync, the importer).

The Files UI then shows only "2 of 2 could not upload" with a Retry button (/home/kayg/Developer/calternal/target/breakit/upload-refused-name-final_1440.png): no reason, and Retry fails the same way.

Repro

Upload a file named 👩‍💻.md in Files.

Expected

  1. Allow ZWJ between emoji (Extended_Pictographic on both sides) and ZWNJ/ZWJ after a letter of a joining script; keep refusing U+200B, U+FEFF and a leading or trailing joiner (the spoofing cases the rule was made for). The same rule must hold in calternald (sync collisions).
  2. A refused name shows its reason in the upload toast ("This name has a character that is not allowed: U+200D") and Retry is not offered for a 400.

Owner: calternal-fs (security-sensitive name policy), so not changed here.

Found by the break-it sweep (#117). Re-run: cd apps/web && bun run build && bun e2e/breakit.mjs --keep <dir> (script on branch job/breakit-fixes). Screenshots: /home/kayg/Developer/calternal/target/breakit (run 1 in run1/, fix checks in verify/).

**Severity:** medium (common emoji and Persian/Indic names cannot be stored; the UI does not say why) ## Problem `calternal_fs::normalize_new_name` refuses U+200B–U+200D and U+FEFF. That blocks: - every emoji ZWJ sequence: `family 👩‍👩‍👧‍👦 flag 🏳️‍🌈.md`, `👩‍💻 notes.md`, `❤️‍🔥.jpg` (400 on upload, mkdir and rename); - ZWNJ (U+200C), which Persian, Urdu and Indic writing need inside words (`نامه‌ای.txt`); - files from a phone or a Mac that already have such names (sync, the importer). The Files UI then shows only **"2 of 2 could not upload"** with a **Retry** button (`/home/kayg/Developer/calternal/target/breakit/upload-refused-name-final_1440.png`): no reason, and Retry fails the same way. ## Repro Upload a file named `👩‍💻.md` in Files. ## Expected 1. Allow ZWJ between emoji (Extended_Pictographic on both sides) and ZWNJ/ZWJ after a letter of a joining script; keep refusing U+200B, U+FEFF and a leading or trailing joiner (the spoofing cases the rule was made for). The same rule must hold in `calternald` (sync collisions). 2. A refused name shows its reason in the upload toast ("This name has a character that is not allowed: U+200D") and Retry is not offered for a 400. Owner: calternal-fs (security-sensitive name policy), so not changed here. Found by the break-it sweep (#117). Re-run: `cd apps/web && bun run build && bun e2e/breakit.mjs --keep <dir>` (script on branch `job/breakit-fixes`). Screenshots: `/home/kayg/Developer/calternal/target/breakit` (run 1 in `run1/`, fix checks in `verify/`).
Author
Owner

Starting the small-bugs batch on job/small-bugs, based on fba83527f2cccf2334934bb1fd0932be7c0e209b.

Starting the small-bugs batch on `job/small-bugs`, based on `fba83527f2cccf2334934bb1fd0932be7c0e209b`.
Author
Owner

The new storage regression test reproduced the issue: normalize_new_name rejected family emoji, emoji modifier sequences, Persian ZWNJ, and Devanagari virama sequences. The fix now permits join controls only in virama, joining-script, or emoji ZWJ contexts. The focused test passes for those valid names and still rejects standalone, Latin-context, and incomplete joiners.

The new storage regression test reproduced the issue: `normalize_new_name` rejected family emoji, emoji modifier sequences, Persian ZWNJ, and Devanagari virama sequences. The fix now permits join controls only in virama, joining-script, or emoji ZWJ contexts. The focused test passes for those valid names and still rejects standalone, Latin-context, and incomplete joiners.
Author
Owner

Focused review finding: normalize_new_name("ب\u{200d}ب.txt") currently accepts ZWJ between Arabic joining letters. The new regression test reproduces this; cargo test -p calternal-fs --test storage new_name_policy_allows_joiners_only_in_writing_sequences fails on that input. Unicode UAX #31 §2.3.1 defines the cursive joining context for ZWNJ, while ZWJ is limited to virama and valid emoji-sequence contexts: https://unicode.org/reports/tr31/tr31-38.html#Limited_Contexts_for_Joining_Controls. I am tightening the check and will rerun the focused test.

Focused review finding: `normalize_new_name("ب\u{200d}ب.txt")` currently accepts ZWJ between Arabic joining letters. The new regression test reproduces this; `cargo test -p calternal-fs --test storage new_name_policy_allows_joiners_only_in_writing_sequences` fails on that input. Unicode UAX #31 §2.3.1 defines the cursive joining context for ZWNJ, while ZWJ is limited to virama and valid emoji-sequence contexts: https://unicode.org/reports/tr31/tr31-38.html#Limited_Contexts_for_Joining_Controls. I am tightening the check and will rerun the focused test.
Author
Owner

Follow-up for the join-control review finding: ZWJ now rejects cursive joining contexts. The focused test failed before the fix on ب\u{200d}ب.txt and passes after it; the same test still accepts the Persian ZWNJ, Indic virama, and emoji ZWJ cases.

Verification:

running 1 test
test new_name_policy_allows_joiners_only_in_writing_sequences ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 40 filtered out; finished in 0.08s

Commit: e2c0f950.

Follow-up for the join-control review finding: ZWJ now rejects cursive joining contexts. The focused test failed before the fix on `ب\u{200d}ب.txt` and passes after it; the same test still accepts the Persian ZWNJ, Indic virama, and emoji ZWJ cases. Verification: ```text running 1 test test new_name_policy_allows_joiners_only_in_writing_sequences ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 40 filtered out; finished in 0.08s ``` Commit: `e2c0f950`.
Author
Owner

Integration status: all small-bugs changes are committed through e2c0f950, and the worktree is clean. As of 2026-09-29 00:55 CEST, origin/dev is deb16f61 and does not contain the pushed job/cli-agent retry change (0c38b723) or job/hig-type change (06757bba). I am holding the single required git merge dev until those branches reach dev, so final browser evidence, the API adversarial round, and gates cover the integrated tree.

Integration status: all small-bugs changes are committed through `e2c0f950`, and the worktree is clean. As of 2026-09-29 00:55 CEST, `origin/dev` is `deb16f61` and does not contain the pushed `job/cli-agent` retry change (`0c38b723`) or `job/hig-type` change (`06757bba`). I am holding the single required `git merge dev` until those branches reach `dev`, so final browser evidence, the API adversarial round, and gates cover the integrated tree.
Author
Owner

Update at 2026-09-29 01:15 CEST: origin/dev is now b708b258, still without job/cli-agent (0c38b723) or job/hig-type (06757bba). A read-only merge preview of job/cli-agent against dev reports a content conflict in crates/calternal-cli/src/remote_commands.rs; the type-scale branch previews cleanly. I have not changed either job's files. My branch is clean at e2c0f950; final integrated verification remains pending.

Update at 2026-09-29 01:15 CEST: `origin/dev` is now `b708b258`, still without `job/cli-agent` (`0c38b723`) or `job/hig-type` (`06757bba`). A read-only merge preview of `job/cli-agent` against `dev` reports a content conflict in `crates/calternal-cli/src/remote_commands.rs`; the type-scale branch previews cleanly. I have not changed either job's files. My branch is clean at `e2c0f950`; final integrated verification remains pending.
Author
Owner

Update at 2026-09-29 01:32 CEST: my branch is clean at 05771f0e0d4f733dba69f1dd9fb2f0257fd87b46. The adversarial runner now has a focused full-API mode so one round can cover #276 and #282/#283 without the larger Search/editor/restart campaigns. Current dev is 5cb977188697c7f1b5dca6d2dd70dc05a6817626; it still does not contain job/cli-agent (0c38b723) or job/hig-type (06757bba). The one required merge preview reports conflicts in Notes plugin startup and the adversarial runner; final integrated verification is pending.

Update at 2026-09-29 01:32 CEST: my branch is clean at `05771f0e0d4f733dba69f1dd9fb2f0257fd87b46`. The adversarial runner now has a focused full-API mode so one round can cover #276 and #282/#283 without the larger Search/editor/restart campaigns. Current `dev` is `5cb977188697c7f1b5dca6d2dd70dc05a6817626`; it still does not contain `job/cli-agent` (`0c38b723`) or `job/hig-type` (`06757bba`). The one required merge preview reports conflicts in Notes plugin startup and the adversarial runner; final integrated verification is pending.
Author
Owner

Update at 2026-09-29 01:59 CEST: dev is a9185e89. job/hig-type is now clean after merging dev into itself at 247b1cbf, but that branch is not yet on dev. job/cli-agent is still clean at 3c4caab7, is not on dev, and its merge preview still conflicts only in test imports in crates/calternal-cli/src/remote_commands.rs. My branch remains clean at 05771f0e0d4f733dba69f1dd9fb2f0257fd87b46; the required final merge and verification remain pending.

Update at 2026-09-29 01:59 CEST: `dev` is `a9185e89`. `job/hig-type` is now clean after merging `dev` into itself at `247b1cbf`, but that branch is not yet on `dev`. `job/cli-agent` is still clean at `3c4caab7`, is not on `dev`, and its merge preview still conflicts only in test imports in `crates/calternal-cli/src/remote_commands.rs`. My branch remains clean at `05771f0e0d4f733dba69f1dd9fb2f0257fd87b46`; the required final merge and verification remain pending.
Author
Owner

small-bugs #142 report

Built

  • #142 accepts contextual ZWJ/ZWNJ name sequences and rejects misplaced joiners (8579a9f7, e2c0f950).
  • #143 adds the long unbroken Share name regression (f33cd91e).
  • #145 adds a hard horizontal-overflow regression for long Tags on phones (13060ab1).
  • #146 was already fixed on the branch; anonymous_requests_get_401_not_500 passed.
  • #153 improves muted Search contrast and adds responsive assertions (75a0944a).
  • #155 removes the phone viewport width floor and adds 200% zoom overflow coverage (232e9d37).
  • #273 adds 44×44 phone Week header target checks (b1b0492d).
  • #276 adds an adversarial probe that checks role denial before config parsing (3900868a).
  • #282/#283 add proxy and Rust API oversized Appearance probes for 200 KiB and 3 MiB bodies (f1126003, a7b1adda).
  • #214 maps transient Calendar Index create failures to retryable 503 (d53a8f5a).
  • #344 repairs Daily note navigation to existing adjacent days with relative links (8322d304).
  • Added focused full-API adversarial mode (4f552baf, a9e848ec, 05771f0e).
  • Merged dev once at 94f1cfd5; resolved Notes imports and preserved both API-only and note-byte-stability adversarial modes (868a0eb1).

Files

Cargo.lock; apps/web/e2e/{breakit,layout-sweep,share}.mjs; apps/web/src/lib/components/search-dialog.svelte; apps/web/src/lib/search/{PhotoGrid,SearchField,SearchPreview,SearchResultRow}.svelte; apps/web/src/routes/layout.css; crates/calternal-fs/{Cargo.toml,src/path.rs,tests/storage.rs}; crates/calternal-notes-core/{src/dayfile.rs,src/lib.rs,tests/vectors/dayfile.json}; crates/plugins/calendar/src/routes.rs; crates/plugins/notes/{migrations/0016_daily_note_navigation.sql,src/lib.rs,src/store.rs}; tests/adversarial/{attack.py,editor-proxy.mjs,run.sh}. The dev merge also integrates files from other jobs.

Head SHA

868a0eb10a7400e1f034c6eb12a28e495c15845f

Verification

Focused Rust regressions passed for #142, #344, #214, #146 and #276. bash -n tests/adversarial/run.sh and git diff --check passed after merge conflict resolution.

  • cargo fmt --check: exit 0, no output.
  • cargo clippy --all-targets -- -D warnings: not run.
  • cargo test: not run.
  • cd apps/web && bun run check output:
    Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/small-bugs/apps/web
    Getting Svelte diagnostics...
    
    /home/kayg/Developer/calternal-wt/small-bugs/apps/web/src/lib/themes.test.ts:278:21
    Error: Object is possibly 'undefined'.
          expect(found, `--layer-${name} defined once`).toHaveLength(1);
          return Number(found[0][1]);
        };
    
    svelte-check found 1 error and 0 warnings in 1 file
    error: script "check" exited with code 1
    
  • cd apps/web && bun run test output:
     Test Files  117 passed (117)
          Tests  764 passed (764)
       Start at  02:20:50
       Duration  177.31s (transform 54%, environment 19%, import 14%, tests 9%, setup 3%)
    
  • Production screenshots and the API adversarial round: not run.

Known gaps

The job/cli-agent retry change (0c38b723) and job/hig-type change (06757bba) were not on dev when it was merged at 94f1cfd5. #348 is therefore not implemented or regression-tested in this branch. The production screenshots and the focused API adversarial round remain pending. Full Rust gates remain pending. The web check failure is in a file supplied by the merged dev tree and outside this issue batch.

Decisions not covered by DESIGN.md

  • Join controls are accepted only in contextual writing or emoji sequences; ZWJ cursive joining is rejected while ZWNJ cursive joining remains allowed, following Unicode UAX #31 §2.3.1.
  • Daily note navigation links only to existing adjacent days; a one-time Notes migration repairs stored links through Replace to retain versions.
  • Search muted text uses a 70% muted / 30% ink blend. SearchPreview gets an explicit token because it is portalled outside .sw.
  • Removing the 320 CSS-pixel minimum lets the Files page reflow at 160/195 CSS-pixel widths under 200% phone zoom.
  • The editor proxy's Appearance Expect: 100-continue threshold matches the Rust route's 64 KiB body limit.

Local OpenSSL verification used system OpenSSL through OPENSSL_NO_VENDOR=1; this does not change product configuration.

# small-bugs #142 report ## Built - #142 accepts contextual ZWJ/ZWNJ name sequences and rejects misplaced joiners (`8579a9f7`, `e2c0f950`). - #143 adds the long unbroken Share name regression (`f33cd91e`). - #145 adds a hard horizontal-overflow regression for long Tags on phones (`13060ab1`). - #146 was already fixed on the branch; `anonymous_requests_get_401_not_500` passed. - #153 improves muted Search contrast and adds responsive assertions (`75a0944a`). - #155 removes the phone viewport width floor and adds 200% zoom overflow coverage (`232e9d37`). - #273 adds 44×44 phone Week header target checks (`b1b0492d`). - #276 adds an adversarial probe that checks role denial before config parsing (`3900868a`). - #282/#283 add proxy and Rust API oversized Appearance probes for 200 KiB and 3 MiB bodies (`f1126003`, `a7b1adda`). - #214 maps transient Calendar Index create failures to retryable 503 (`d53a8f5a`). - #344 repairs Daily note navigation to existing adjacent days with relative links (`8322d304`). - Added focused full-API adversarial mode (`4f552baf`, `a9e848ec`, `05771f0e`). - Merged `dev` once at `94f1cfd5`; resolved Notes imports and preserved both API-only and note-byte-stability adversarial modes (`868a0eb1`). ## Files `Cargo.lock`; `apps/web/e2e/{breakit,layout-sweep,share}.mjs`; `apps/web/src/lib/components/search-dialog.svelte`; `apps/web/src/lib/search/{PhotoGrid,SearchField,SearchPreview,SearchResultRow}.svelte`; `apps/web/src/routes/layout.css`; `crates/calternal-fs/{Cargo.toml,src/path.rs,tests/storage.rs}`; `crates/calternal-notes-core/{src/dayfile.rs,src/lib.rs,tests/vectors/dayfile.json}`; `crates/plugins/calendar/src/routes.rs`; `crates/plugins/notes/{migrations/0016_daily_note_navigation.sql,src/lib.rs,src/store.rs}`; `tests/adversarial/{attack.py,editor-proxy.mjs,run.sh}`. The `dev` merge also integrates files from other jobs. ## Head SHA `868a0eb10a7400e1f034c6eb12a28e495c15845f` ## Verification Focused Rust regressions passed for #142, #344, #214, #146 and #276. `bash -n tests/adversarial/run.sh` and `git diff --check` passed after merge conflict resolution. - `cargo fmt --check`: exit 0, no output. - `cargo clippy --all-targets -- -D warnings`: not run. - `cargo test`: not run. - `cd apps/web && bun run check` output: ```text Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/small-bugs/apps/web Getting Svelte diagnostics... /home/kayg/Developer/calternal-wt/small-bugs/apps/web/src/lib/themes.test.ts:278:21 Error: Object is possibly 'undefined'. expect(found, `--layer-${name} defined once`).toHaveLength(1); return Number(found[0][1]); }; svelte-check found 1 error and 0 warnings in 1 file error: script "check" exited with code 1 ``` - `cd apps/web && bun run test` output: ```text Test Files 117 passed (117) Tests 764 passed (764) Start at 02:20:50 Duration 177.31s (transform 54%, environment 19%, import 14%, tests 9%, setup 3%) ``` - Production screenshots and the API adversarial round: not run. ## Known gaps The `job/cli-agent` retry change (`0c38b723`) and `job/hig-type` change (`06757bba`) were not on `dev` when it was merged at `94f1cfd5`. #348 is therefore not implemented or regression-tested in this branch. The production screenshots and the focused API adversarial round remain pending. Full Rust gates remain pending. The web check failure is in a file supplied by the merged `dev` tree and outside this issue batch. ## Decisions not covered by DESIGN.md - Join controls are accepted only in contextual writing or emoji sequences; ZWJ cursive joining is rejected while ZWNJ cursive joining remains allowed, following [Unicode UAX #31 §2.3.1](https://unicode.org/reports/tr31/tr31-38.html#Limited_Contexts_for_Joining_Controls). - Daily note navigation links only to existing adjacent days; a one-time Notes migration repairs stored links through Replace to retain versions. - Search muted text uses a 70% muted / 30% ink blend. SearchPreview gets an explicit token because it is portalled outside `.sw`. - Removing the 320 CSS-pixel minimum lets the Files page reflow at 160/195 CSS-pixel widths under 200% phone zoom. - The editor proxy's Appearance `Expect: 100-continue` threshold matches the Rust route's 64 KiB body limit. Local OpenSSL verification used system OpenSSL through `OPENSSL_NO_VENDOR=1`; this does not change product configuration.
Author
Owner

Claude: small-bugs merge REVERTED on dev (43a69592) — startup-recovery bug

crates/plugins/notes test tests::retitle_replays_after_each_persisted_step fails after the merge with a 500 from recover(). Cause: commit 8322d304 ("Repair relative navigation between Daily notes") adds migrate_daily_note_navigation to recover(), and it runs SELECT id FROM users against the Notes database. The users table belongs to the auth store, not to notes.sqlite; in the test (and on any instance where the Notes DB is separate) this errors, and recover() fails at startup. A plugin must not read another crate's tables.

Fix:

  1. Enumerate Users from the Home directories through calternal-fs (the way the Notes reconcile already does) or from the Notes plugin's own tables, never from auth's users table.
  2. The migration must never fail startup: log and retry later on error; mark done per User.
  3. Run the whole cargo test -p calternal-plugin-notes suite (it was not run) plus the focused regressions; quote verbatim.
  4. Because the merge was reverted on dev, re-merging the same commits will not bring them back. Make a new branch job/small-bugs-2 from current dev, git cherry-pick your non-merge commits from job/small-bugs (in order), fix the migration there, run the gates, and push job/small-bugs-2. Also include #348 now (the CLI retry change is on dev).
## Claude: small-bugs merge REVERTED on dev (43a69592) — startup-recovery bug `crates/plugins/notes` test `tests::retitle_replays_after_each_persisted_step` fails after the merge with a 500 from `recover()`. Cause: commit 8322d304 ("Repair relative navigation between Daily notes") adds `migrate_daily_note_navigation` to `recover()`, and it runs `SELECT id FROM users` against the **Notes** database. The `users` table belongs to the auth store, not to `notes.sqlite`; in the test (and on any instance where the Notes DB is separate) this errors, and `recover()` fails at startup. A plugin must not read another crate's tables. Fix: 1. Enumerate Users from the Home directories through calternal-fs (the way the Notes reconcile already does) or from the Notes plugin's own tables, never from auth's `users` table. 2. The migration must never fail startup: log and retry later on error; mark done per User. 3. Run the **whole** `cargo test -p calternal-plugin-notes` suite (it was not run) plus the focused regressions; quote verbatim. 4. Because the merge was reverted on dev, re-merging the same commits will not bring them back. Make a new branch `job/small-bugs-2` from current dev, `git cherry-pick` your non-merge commits from job/small-bugs (in order), fix the migration there, run the gates, and push `job/small-bugs-2`. Also include #348 now (the CLI retry change is on dev).
Author
Owner

Starting the rework on job/small-bugs-2, based on 43a69592b70254825fb9b82092f1b883ba65fc3a (dev). I will cherry-pick the listed non-merge commits in order, make the Daily note migration enumerate Homes and complete per User without failing startup, add #348, then run the requested gates.

Starting the rework on `job/small-bugs-2`, based on `43a69592b70254825fb9b82092f1b883ba65fc3a` (`dev`). I will cherry-pick the listed non-merge commits in order, make the Daily note migration enumerate Homes and complete per User without failing startup, add #348, then run the requested gates.
Author
Owner

Finding: on the restored branch, cargo test -p calternal-plugin-notes retitle_replays_after_each_persisted_step -- --nocapture fails at recover(&root, &db).await.unwrap() with ApiError(500, ... Internal ...). The Notes test database has no auth users table; migrate_daily_note_navigation queries it and propagates the SQL error through startup recovery.

Finding: on the restored branch, `cargo test -p calternal-plugin-notes retitle_replays_after_each_persisted_step -- --nocapture` fails at `recover(&root, &db).await.unwrap()` with `ApiError(500, ... Internal ...)`. The Notes test database has no auth `users` table; `migrate_daily_note_navigation` queries it and propagates the SQL error through startup recovery.
Author
Owner

Finding during the required web check after merging dev: bun run check rejected font-size: var(--text-dense, 0.86rem) in apps/web/src/lib/components/search-dialog.svelte:1317 because the literal bypassed the shared type-token guard. The --text-dense token is defined in packages/ui/src/tokens.css, so I removed the redundant fallback. The rerun passed: svelte-check found 0 errors and 0 warnings. Fix commit: bd3f735a.

Finding during the required web check after merging `dev`: `bun run check` rejected `font-size: var(--text-dense, 0.86rem)` in `apps/web/src/lib/components/search-dialog.svelte:1317` because the literal bypassed the shared type-token guard. The `--text-dense` token is defined in `packages/ui/src/tokens.css`, so I removed the redundant fallback. The rerun passed: `svelte-check found 0 errors and 0 warnings`. Fix commit: `bd3f735a`.
Author
Owner

Resume at 2026-09-29 06:35 CEST: branch job/small-bugs-2 is clean at bd3f735a. The earlier work already includes the Home-based per-User Daily note migration, CLI transient 502 retry, the dev merge, and the Search token fix.

The whole cargo test -p calternal-plugin-notes suite passed: 110 passed; 0 failed, including retitle_replays_after_each_persisted_step and both new migration regressions. The focused CLI 502 polling regression passed. cargo fmt --check passed with no output. Running the remaining final gates now.

Resume at 2026-09-29 06:35 CEST: branch `job/small-bugs-2` is clean at `bd3f735a`. The earlier work already includes the Home-based per-User Daily note migration, CLI transient 502 retry, the `dev` merge, and the Search token fix. The whole `cargo test -p calternal-plugin-notes` suite passed: `110 passed; 0 failed`, including `retitle_replays_after_each_persisted_step` and both new migration regressions. The focused CLI 502 polling regression passed. `cargo fmt --check` passed with no output. Running the remaining final gates now.
Author
Owner

Finding for #348: the shared send_with_retry helper retries transport errors only for safe HTTP methods. The CLI token poll is a POST, so a dropped connection still ended login, and the helper retried only selected 5xx statuses. I added a bounded retry in the device-login polling loop for transport errors, any 5xx, and 429. The regression now serves a dropped connection, HTTP 500 and HTTP 502 in sequence and checks the retryable failure message after the limit.

Finding for #348: the shared `send_with_retry` helper retries transport errors only for safe HTTP methods. The CLI token poll is a POST, so a dropped connection still ended login, and the helper retried only selected 5xx statuses. I added a bounded retry in the device-login polling loop for transport errors, any 5xx, and 429. The regression now serves a dropped connection, HTTP 500 and HTTP 502 in sequence and checks the retryable failure message after the limit.
Author
Owner

Resume at 2026-09-29 07:13 CEST: branch job/small-bugs-2 is clean at 17598901711658e84f83060de4f29dcffbdaf717, which is also on origin/job/small-bugs-2. The Notes suite, focused CLI polling regression, Rust fmt, web check and web unit tests have passed in this worktree. The earlier workspace clippy and Rust test logs stopped mid-build during the restart, so I am running the remaining workspace gates now. The required dev merge already happened once at de14b938; I will not repeat it.

Resume at 2026-09-29 07:13 CEST: branch `job/small-bugs-2` is clean at `17598901711658e84f83060de4f29dcffbdaf717`, which is also on `origin/job/small-bugs-2`. The Notes suite, focused CLI polling regression, Rust fmt, web check and web unit tests have passed in this worktree. The earlier workspace clippy and Rust test logs stopped mid-build during the restart, so I am running the remaining workspace gates now. The required `dev` merge already happened once at `de14b938`; I will not repeat it.
Author
Owner

Production browser review finding at 2026-09-29 07:31 CEST: the focused breakit.mjs --quick --only search run saved 48 Search screens and returned 301 automated findings, mainly contrast estimates over translucent glass. For search@320-paper, the probe estimates the section label on RGB(139,142,142) at 2.32:1, but the saved production screenshot has RGB(240,240,242) next to that label (checked from the PNG pixel data with ffmpeg). The probe's comment calls glass contrast an estimate; this specific contrast finding does not prove a real text contrast failure. The run also reports page-hscroll on simulated 200% zoom because Chromium reports a 320px layout viewport for a 160/195px mobile emulation, and expected 5xx in the intentional network-failure campaign. Artifacts remain uncommitted at target/breakit-final/report.json and target/breakit-review/. I will list these limits in the final report; no test expectations were changed.

Production browser review finding at 2026-09-29 07:31 CEST: the focused `breakit.mjs --quick --only search` run saved 48 Search screens and returned 301 automated findings, mainly contrast estimates over translucent glass. For `search@320-paper`, the probe estimates the section label on RGB(139,142,142) at 2.32:1, but the saved production screenshot has RGB(240,240,242) next to that label (checked from the PNG pixel data with ffmpeg). The probe's comment calls glass contrast an estimate; this specific contrast finding does not prove a real text contrast failure. The run also reports page-hscroll on simulated 200% zoom because Chromium reports a 320px layout viewport for a 160/195px mobile emulation, and expected 5xx in the intentional network-failure campaign. Artifacts remain uncommitted at `target/breakit-final/report.json` and `target/breakit-review/`. I will list these limits in the final report; no test expectations were changed.
Author
Owner

At 2026-09-29 07:41 CEST the full workspace clippy gate is still compiling under the 4-job cap on the shared host; no diagnostic has appeared. The one time-boxed real-server API adversarial round is in progress. It has found one non-SLOW Appearance 3 MiB response (502 from the local proxy instead of 413), plus a DAV alarm accepted-hostile-input result outside this batch; the remaining reported delays are marked SLOW. I am recording those findings and will give exact final gate results and unresolved items. The branch remains clean at 17598901.

At 2026-09-29 07:41 CEST the full workspace clippy gate is still compiling under the 4-job cap on the shared host; no diagnostic has appeared. The one time-boxed real-server API adversarial round is in progress. It has found one non-SLOW Appearance 3 MiB response (`502` from the local proxy instead of `413`), plus a DAV alarm accepted-hostile-input result outside this batch; the remaining reported delays are marked SLOW. I am recording those findings and will give exact final gate results and unresolved items. The branch remains clean at `17598901`.
Author
Owner

Finding for #155 after the production browser review: the original fix removed min-width: 320px from apps/web/src/routes/layout.css, but packages/ui/src/tokens.css still applied the same minimum to html, body. The real Files sweep therefore reported a 320px layout viewport for simulated 160px and 195px CSS widths. A minimal Chromium page at 195px did not clamp, which isolated the remaining floor to shared CSS.

Commit d34e6266 removes the shared floor (small addition outside the prior file set, required by #155). With a rebuilt production app, the existing breakit.mjs --quick --only files-hostile 200% width assertion passed at both 160px and 195px; that focused run exited 0. bun run check reported svelte-check found 0 errors and 0 warnings, and bun run test reported 120 passed (120) files and 782 passed (782) tests. The screenshot sweep still records soft overlap and partly offscreen button findings at extreme zoom; artifacts are in target/breakit-files-zoomfix and are not committed.

Finding for #155 after the production browser review: the original fix removed `min-width: 320px` from `apps/web/src/routes/layout.css`, but `packages/ui/src/tokens.css` still applied the same minimum to `html, body`. The real Files sweep therefore reported a 320px layout viewport for simulated 160px and 195px CSS widths. A minimal Chromium page at 195px did not clamp, which isolated the remaining floor to shared CSS. Commit `d34e6266` removes the shared floor (small addition outside the prior file set, required by #155). With a rebuilt production app, the existing `breakit.mjs --quick --only files-hostile` 200% width assertion passed at both 160px and 195px; that focused run exited 0. `bun run check` reported `svelte-check found 0 errors and 0 warnings`, and `bun run test` reported `120 passed (120)` files and `782 passed (782)` tests. The screenshot sweep still records soft overlap and partly offscreen button findings at extreme zoom; artifacts are in `target/breakit-files-zoomfix` and are not committed.
Author
Owner

At 2026-09-29 08:12 CEST, the new shared CSS width-floor fix is committed and pushed as d34e6266; the branch is clean. The rebuilt production Files sweep passes its 160px and 195px width assertions, and the Tags sweep exits 0. Calendar Week still has a 206–207px minimum-content width at those extreme zoom sizes; I recorded it on #155. cargo fmt --check, bun run check, and bun run test have passed at this head. The full workspace clippy gate is still running under CARGO_BUILD_JOBS=4; the full workspace Rust test gate is queued behind it. I will report exact exit codes, not infer them from partial logs.

At 2026-09-29 08:12 CEST, the new shared CSS width-floor fix is committed and pushed as `d34e6266`; the branch is clean. The rebuilt production Files sweep passes its 160px and 195px width assertions, and the Tags sweep exits 0. Calendar Week still has a 206–207px minimum-content width at those extreme zoom sizes; I recorded it on #155. `cargo fmt --check`, `bun run check`, and `bun run test` have passed at this head. The full workspace clippy gate is still running under `CARGO_BUILD_JOBS=4`; the full workspace Rust test gate is queued behind it. I will report exact exit codes, not infer them from partial logs.
Author
Owner

At 2026-09-29 08:30 CEST, the workspace cargo clippy --all-targets -- -D warnings gate completed at the pushed head. The first invocation finished its Cargo check in 82m 23s, then my zsh wrapper failed because status is read-only; the cached confirmation invocation produced Finished dev profile [unoptimized + debuginfo] target(s) in 1m 10s and CLIPPY_EXIT=0. The full workspace cargo test gate is running now. No code changed after d34e6266.

At 2026-09-29 08:30 CEST, the workspace `cargo clippy --all-targets -- -D warnings` gate completed at the pushed head. The first invocation finished its Cargo check in 82m 23s, then my zsh wrapper failed because `status` is read-only; the cached confirmation invocation produced `Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 10s` and `CLIPPY_EXIT=0`. The full workspace `cargo test` gate is running now. No code changed after `d34e6266`.
Author
Owner

At 2026-09-29 08:46 CEST, cargo test is still compiling the full workspace with the required four-job cap. It has reached calternal-plugin-calendar; no test case has run or failed in this invocation yet. The branch remains clean at pushed head d34e6266. I will report the final gate exit code once it exists, or mark the gate pending if the job time cap ends first.

At 2026-09-29 08:46 CEST, `cargo test` is still compiling the full workspace with the required four-job cap. It has reached `calternal-plugin-calendar`; no test case has run or failed in this invocation yet. The branch remains clean at pushed head `d34e6266`. I will report the final gate exit code once it exists, or mark the gate pending if the job time cap ends first.
Author
Owner

Finding and fix: The Files upload toast still hid the reason for a refused filename and offered Retry after a 400. Commit e5750bfa makes the first failure reason visible in the collapsed toast, identifies U+200B/U+FEFF and disallowed joiners after server refusal, and prevents unchanged 400 requests from being retried. Focused queue and component regressions pass (6 tests); web check passes with 0 errors and 0 warnings. The full web suite hit 5-second timeouts in unrelated menu focus and Mail tests under shared-host load; both test files pass alone. Production screenshot verification is in progress against a branch server build.

Finding and fix: The Files upload toast still hid the reason for a refused filename and offered Retry after a 400. Commit e5750bfa makes the first failure reason visible in the collapsed toast, identifies U+200B/U+FEFF and disallowed joiners after server refusal, and prevents unchanged 400 requests from being retried. Focused queue and component regressions pass (6 tests); web check passes with 0 errors and 0 warnings. The full web suite hit 5-second timeouts in unrelated menu focus and Mail tests under shared-host load; both test files pass alone. Production screenshot verification is in progress against a branch server build.
Author
Owner

small-bugs-2 final report for #142

Built

  • Restored the small-bugs batch on job/small-bugs-2: contextual Unicode join controls in Files (#142), long Share-name containment (#143), Tags overflow regression (#145), muted Search contrast (#153), phone zoom reflow (#155), Week touch targets (#273), admin config denial before parsing (#276), oversized Appearance probes (#282/#283), retryable Calendar Index writes (#214), and Daily note relative navigation (#344). The anonymous 401 regression for #146 already passed on the branch.
  • Repaired the Daily note migration that caused the prior merge revert. It enumerates Homes through calternal-fs, records completion per User, and logs failures for retry rather than failing Notes startup.
  • Added #348: bounded retries for transient CLI device-login poll transport errors, 429, and 5xx, with a regression that includes 502.
  • Completed #142's Files UI behavior in e5750bfa: the collapsed upload toast shows a refused name's reason, including U+200B; a 400 has no Retry action and cannot be resent unchanged.
  • Merged dev once at de14b938, resolved both sides, and pushed this branch. No merge to dev or deploy was performed.

Files

Principal changed files: crates/calternal-fs/{src/path.rs,tests/storage.rs}; crates/plugins/notes/{migrations/0016_daily_note_navigation.sql,migrations/0017_daily_note_navigation_per_user.sql,src/lib.rs,src/store.rs}; crates/calternal-cli/src/login.rs; crates/plugins/calendar/src/routes.rs; apps/web/src/lib/files/{uploads.svelte.ts,uploads.svelte.test.ts,UploadToast.svelte,UploadToast.svelte.test.ts}; apps/web/src/lib/search/{PhotoGrid,SearchField,SearchPreview,SearchResultRow}.svelte; apps/web/src/lib/components/search-dialog.svelte; apps/web/src/routes/layout.css; packages/ui/src/tokens.css; apps/web/e2e/{breakit,layout-sweep,share}.mjs; tests/adversarial/{attack.py,editor-proxy.mjs}; Cargo.lock. The dev merge also brought other jobs' files into this branch.

Head SHA

e5750bfa3d1434c0d66ae77e451e23966fb42c56 (same SHA on origin/job/small-bugs-2); worktree clean.

Gates and evidence

Commands used CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4, and TMPDIR=$PWD/target/tmp. The output excerpts below are verbatim.

  • cargo fmt --check: exit 0, no output.
  • cargo clippy --all-targets -- -D warnings: exit 0 on cached confirmation:
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 10s
    
  • cargo test -p calternal-plugin-notes:
    test result: ok. 110 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 39.78s
    
  • Focused CLI polling regression:
    test login::tests::device_login_retries_a_transient_token_poll_and_says_when_retry_remains_possible ... ok
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 21 filtered out; finished in 0.77s
    
  • cargo test: exit 101 in the unrelated calternal-collab/tests/two_clients.rs crash/debounce test under shared-host load:
    thread 'crash_before_debounce_reloads_only_complete_markdown' (431225) panicked at crates/calternal-collab/tests/two_clients.rs:89:5:
    assertion `left == right` failed: 
      left: Some(101)
     right: Some(137)
    test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.38s
    
    Direct rerun of that compiled test binary:
    test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.88s
    
    The failure was recorded on #385; no test expectation was changed.
  • cd apps/web && bun run check:
    svelte-check found 0 errors and 0 warnings
    
  • cd apps/web && bun run test --maxWorkers 2: exit 0:
     Test Files  121 passed (121)
          Tests  784 passed (784)
    
    Two default-concurrency runs had 5-second timeouts in unrelated menu-focus/Mail tests; both test files passed alone. The lower-concurrency run passed the entire suite without changing expectations.
  • cd apps/web && bun run build: exit 0:
    ✓ built in 1m 40s
    
  • Real local API returned 400 for bad\u200bname.txt; the current production web build showed This name has a character that is not allowed: U+200B and no Retry action. Playwright captured all six Files states at 390, 820, and 1440 px in light and dark; its assertions and page-error check passed:
    UPLOAD_RESPONSE 400 http://localhost:12069/api/v1/files/uploads
    TOAST_TEXT 1 of 1 could not upload
    This name has a character that is not allowed: U+200B
    PASS phone light: refusal visible, no Retry
    PASS phone dark: refusal visible, no Retry
    PASS tablet light: refusal visible, no Retry
    PASS tablet dark: refusal visible, no Retry
    PASS desktop light: refusal visible, no Retry
    PASS desktop dark: refusal visible, no Retry
    UPLOAD_TOAST_EVIDENCE_PASSED
    
    Screenshots remain uncommitted at target/upload-toast-review/files-upload-refusal-*.png. The local API binary embeds a different checkout's web assets, so Playwright served this branch's compiled production assets at the API origin while retaining the real server API.
  • The one time-boxed real-server API adversarial round exited 1 with SLOW load findings plus existing non-SLOW Appearance proxy 502, DAV discovery 207, and DAV alarm acceptance findings. These were recorded on #394/#379. The server remained alive. The Files and Tags focused production sweeps passed; Calendar Week still overflows at 160/195 CSS-pixel extreme zoom (#155).
  • git diff --check passed. cargo clean completed (Removed 216 files, 83.9MiB total after the canceled extra server build), and web build output was removed.

Known gaps

The full workspace Rust test gate is red on the load-sensitive collab test. The adversarial round has non-SLOW findings tracked on #394/#379. Calendar Week extreme zoom and some soft Files overlap remain on #155; those are outside this batch. Screenshot files could not be uploaded with scripts/fj, whose issue commands have no attachment option; they remain in the worktree for orchestrator review.

Decisions not covered by DESIGN.md

  • A 400 upload refusal is terminal for the unchanged request. The client reports an invisible code point only after the server refuses it; the server remains the validation authority. Other 400 cases use The server refused this file name or folder.
  • The migration uses Homes rather than auth tables and tracks completion per User, so an error can be retried without blocking startup.
  • ZWNJ cursive joining remains allowed; ZWJ cursive joining is refused. Emoji ZWJ context follows Unicode UAX #31 limited contexts.
  • Search muted text uses the shared dense role token and a 70% muted / 30% ink blend. Removing the shared 320px html, body minimum permits Files to reflow at 200% phone zoom.
# small-bugs-2 final report for #142 ## Built - Restored the small-bugs batch on `job/small-bugs-2`: contextual Unicode join controls in Files (#142), long Share-name containment (#143), Tags overflow regression (#145), muted Search contrast (#153), phone zoom reflow (#155), Week touch targets (#273), admin config denial before parsing (#276), oversized Appearance probes (#282/#283), retryable Calendar Index writes (#214), and Daily note relative navigation (#344). The anonymous 401 regression for #146 already passed on the branch. - Repaired the Daily note migration that caused the prior merge revert. It enumerates Homes through `calternal-fs`, records completion per User, and logs failures for retry rather than failing Notes startup. - Added #348: bounded retries for transient CLI device-login poll transport errors, 429, and 5xx, with a regression that includes 502. - Completed #142's Files UI behavior in `e5750bfa`: the collapsed upload toast shows a refused name's reason, including U+200B; a 400 has no Retry action and cannot be resent unchanged. - Merged `dev` once at `de14b938`, resolved both sides, and pushed this branch. No merge to `dev` or deploy was performed. ## Files Principal changed files: `crates/calternal-fs/{src/path.rs,tests/storage.rs}`; `crates/plugins/notes/{migrations/0016_daily_note_navigation.sql,migrations/0017_daily_note_navigation_per_user.sql,src/lib.rs,src/store.rs}`; `crates/calternal-cli/src/login.rs`; `crates/plugins/calendar/src/routes.rs`; `apps/web/src/lib/files/{uploads.svelte.ts,uploads.svelte.test.ts,UploadToast.svelte,UploadToast.svelte.test.ts}`; `apps/web/src/lib/search/{PhotoGrid,SearchField,SearchPreview,SearchResultRow}.svelte`; `apps/web/src/lib/components/search-dialog.svelte`; `apps/web/src/routes/layout.css`; `packages/ui/src/tokens.css`; `apps/web/e2e/{breakit,layout-sweep,share}.mjs`; `tests/adversarial/{attack.py,editor-proxy.mjs}`; `Cargo.lock`. The `dev` merge also brought other jobs' files into this branch. ## Head SHA `e5750bfa3d1434c0d66ae77e451e23966fb42c56` (same SHA on `origin/job/small-bugs-2`); worktree clean. ## Gates and evidence Commands used `CARGO_PROFILE_DEV_DEBUG=line-tables-only`, `CARGO_INCREMENTAL=0`, `CARGO_BUILD_JOBS=4`, and `TMPDIR=$PWD/target/tmp`. The output excerpts below are verbatim. - `cargo fmt --check`: exit 0, no output. - `cargo clippy --all-targets -- -D warnings`: exit 0 on cached confirmation: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 10s ``` - `cargo test -p calternal-plugin-notes`: ```text test result: ok. 110 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 39.78s ``` - Focused CLI polling regression: ```text test login::tests::device_login_retries_a_transient_token_poll_and_says_when_retry_remains_possible ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 21 filtered out; finished in 0.77s ``` - `cargo test`: exit 101 in the unrelated `calternal-collab/tests/two_clients.rs` crash/debounce test under shared-host load: ```text thread 'crash_before_debounce_reloads_only_complete_markdown' (431225) panicked at crates/calternal-collab/tests/two_clients.rs:89:5: assertion `left == right` failed: left: Some(101) right: Some(137) test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.38s ``` Direct rerun of that compiled test binary: ```text test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.88s ``` The failure was recorded on #385; no test expectation was changed. - `cd apps/web && bun run check`: ```text svelte-check found 0 errors and 0 warnings ``` - `cd apps/web && bun run test --maxWorkers 2`: exit 0: ```text Test Files 121 passed (121) Tests 784 passed (784) ``` Two default-concurrency runs had 5-second timeouts in unrelated menu-focus/Mail tests; both test files passed alone. The lower-concurrency run passed the entire suite without changing expectations. - `cd apps/web && bun run build`: exit 0: ```text ✓ built in 1m 40s ``` - Real local API returned 400 for `bad\u200bname.txt`; the current production web build showed `This name has a character that is not allowed: U+200B` and no Retry action. Playwright captured all six Files states at 390, 820, and 1440 px in light and dark; its assertions and page-error check passed: ```text UPLOAD_RESPONSE 400 http://localhost:12069/api/v1/files/uploads TOAST_TEXT 1 of 1 could not upload This name has a character that is not allowed: U+200B PASS phone light: refusal visible, no Retry PASS phone dark: refusal visible, no Retry PASS tablet light: refusal visible, no Retry PASS tablet dark: refusal visible, no Retry PASS desktop light: refusal visible, no Retry PASS desktop dark: refusal visible, no Retry UPLOAD_TOAST_EVIDENCE_PASSED ``` Screenshots remain uncommitted at `target/upload-toast-review/files-upload-refusal-*.png`. The local API binary embeds a different checkout's web assets, so Playwright served this branch's compiled production assets at the API origin while retaining the real server API. - The one time-boxed real-server API adversarial round exited 1 with SLOW load findings plus existing non-SLOW Appearance proxy 502, DAV discovery 207, and DAV alarm acceptance findings. These were recorded on #394/#379. The server remained alive. The Files and Tags focused production sweeps passed; Calendar Week still overflows at 160/195 CSS-pixel extreme zoom (#155). - `git diff --check` passed. `cargo clean` completed (`Removed 216 files, 83.9MiB total` after the canceled extra server build), and web build output was removed. ## Known gaps The full workspace Rust test gate is red on the load-sensitive collab test. The adversarial round has non-SLOW findings tracked on #394/#379. Calendar Week extreme zoom and some soft Files overlap remain on #155; those are outside this batch. Screenshot files could not be uploaded with `scripts/fj`, whose issue commands have no attachment option; they remain in the worktree for orchestrator review. ## Decisions not covered by DESIGN.md - A 400 upload refusal is terminal for the unchanged request. The client reports an invisible code point only after the server refuses it; the server remains the validation authority. Other 400 cases use `The server refused this file name or folder.` - The migration uses Homes rather than auth tables and tracks completion per User, so an error can be retried without blocking startup. - ZWNJ cursive joining remains allowed; ZWJ cursive joining is refused. Emoji ZWJ context follows Unicode UAX #31 limited contexts. - Search muted text uses the shared dense role token and a 70% muted / 30% ink blend. Removing the shared 320px `html, body` minimum permits Files to reflow at 200% phone zoom.
kayg closed this issue 2026-09-29 08:29:11 +00:00
Author
Owner

Starting visual-resume work on job/ui-small-2 at 87a0a877495eefd55a1abe4b2e2f140051733839; the branch's current dev base is 191b179baac3ef4f5bebfe07ce91c4b7a887ace2.

The job prompt says this is the Files filename visual rejection. The current closed #142 body and its history describe the earlier Unicode filename validation batch, so this progress note records the mismatch. I am continuing the shared FileName.svelte, row rhythm and Files production evidence work from the requested review.

Starting visual-resume work on `job/ui-small-2` at `87a0a877495eefd55a1abe4b2e2f140051733839`; the branch's current `dev` base is `191b179baac3ef4f5bebfe07ce91c4b7a887ace2`. The job prompt says this is the Files filename visual rejection. The current closed #142 body and its history describe the earlier Unicode filename validation batch, so this progress note records the mismatch. I am continuing the shared `FileName.svelte`, row rhythm and Files production evidence work from the requested review.
Author
Owner

Finding from the resumed branch: FileName.svelte rendered .stem and .ext as separate children, then set direction: rtl on the filename box to reverse CSS clamping. That let a long suffix occupy its own edge-aligned box. Files list rows also did not pass an explicit one-line desktop/two-line phone limit. I replaced this with one text run, cached canvas middle cuts, and a shared width observer. The production browser run is pending the local server build.

The requested task is a visual continuation. The closed #142 body still describes Unicode name validation, as noted in my start comment.

Finding from the resumed branch: `FileName.svelte` rendered `.stem` and `.ext` as separate children, then set `direction: rtl` on the filename box to reverse CSS clamping. That let a long suffix occupy its own edge-aligned box. Files list rows also did not pass an explicit one-line desktop/two-line phone limit. I replaced this with one text run, cached canvas middle cuts, and a shared width observer. The production browser run is pending the local server build. The requested task is a visual continuation. The closed #142 body still describes Unicode name validation, as noted in my start comment.
Author
Owner

Delivered

  • Shared FileName.svelte keeps each filename in one text run. It measures grapheme-safe middle cuts with a bounded canvas cache and one shared ResizeObserver; it keeps the extension at the logical end. The full name remains in title and aria-label.
  • Files list names wrap to at most two lines on phone and one line at tablet and desktop widths. The title/meta stack uses a scaled 0.5rem gap.
  • The Files E2E asserts suffix glyph adjacency within 1ch on the same line, full accessible name, RTL direction, middle truncation, and line limits across five API-created filenames.
  • The sidebar ignores navigation targets without URLs when tracking same-origin history depth.

Production screenshot evidence

All 54 PNGs are attached to this issue: six full-page captures, 30 row crops and 18 zoom crops. They use this worktree's production SPA build and a real local API server.

Width Light Dark
390px phone light dark
820px tablet light dark
1440px desktop light dark

3× row crops

Zoom crops

Decisions

  • DESIGN §34 sets the filename suffix and line-count behavior. The implementation uses a balanced grapheme-safe middle cut and a 512-entry LRU keyed by width and text metrics; no per-row observer runs during scrolling.
  • Settings AccountRow has no shared paragraph-gap token. I used 0.5rem * --ui-scale (8px at default scale) for the wrapped title/meta stack because the prior 0.25rem gap looked too tight.
  • DESIGN §34 states the phone two-line and desktop one-line limits. I applied the one-line list limit at 820px tablet width too.
  • E2E used a prebuilt agenda-413 server binary whose server, filesystem and Files plugin crates match dev; CALTERNAL_E2E_ASSET_OVERRIDE=1 served this worktree's production SPA.

Files

  • apps/web/e2e/analytics.mjs, files.mjs, harness.mjs, photos.mjs
  • apps/web/src/lib/components/analytics/BklitAnalytics.tsx and analytics/vendor/bklit/charts/heatmap/heatmap-y-axis.tsx
  • apps/web/src/lib/components/app-sidebar.svelte
  • apps/web/src/lib/files/FileCollection.svelte.test.ts, PublicLinkPage.svelte, UploadToast.svelte
  • apps/web/src/lib/photos/TimelineScrubber.svelte
  • apps/web/src/lib/search/SearchResultRow.svelte and SearchResultRow.svelte.test.ts
  • packages/ui/src/components/calendar/ItemPreview.svelte
  • packages/ui/src/components/files/FileCollection.svelte and FileName.svelte
  • packages/ui/src/tokens.css

Gates

cargo fmt --check
(no stdout; exit 0)

bun run check
svelte-check found 0 errors and 0 warnings

bun run test
 Test Files  125 passed (125)
      Tests  803 passed (803)
   Start at  14:12:25
   Duration  106.70s (transform 58%, environment 15%, import 15%, tests 9%, setup 3%)

bun run build
✓ built in 31.28s
  Wrote site to "build"
  ✔ done

CALTERNAL_E2E_ASSET_OVERRIDE=1 bun e2e/files.mjs --screenshots target/files-name-review --screenshots-only --shot-surface files
FILES SCREENSHOTS PASSED
CSP REPORTS files: 0 across 7 pages

No Rust source or contract changed, so Rust clippy/tests were not run. node --check apps/web/e2e/files.mjs and git diff --check also passed.

Known gaps

  • The filename screenshot path uses Playwright's native row crops and passed. This host's sharp native module could not load libstdc++.so.6; the separate selection/header 2× crop path still uses sharp and was not part of this capture.
  • The branch-local server build was stopped after long shared-host contention. The compatible prebuilt server above supplied the real API during E2E.
  • Issue #142 is closed and its body covers Unicode join-control validation, not this UI review. I posted this report and evidence there without changing the issue state.

Commits

Head: b65058175f7dc2578c78fe2d6098101bfb42dfc8. Merged dev once before final gates as instructed; no push or deploy was performed.

## Delivered - Shared `FileName.svelte` keeps each filename in one text run. It measures grapheme-safe middle cuts with a bounded canvas cache and one shared `ResizeObserver`; it keeps the extension at the logical end. The full name remains in `title` and `aria-label`. - Files list names wrap to at most two lines on phone and one line at tablet and desktop widths. The title/meta stack uses a scaled 0.5rem gap. - The Files E2E asserts suffix glyph adjacency within 1ch on the same line, full accessible name, RTL direction, middle truncation, and line limits across five API-created filenames. - The sidebar ignores navigation targets without URLs when tracking same-origin history depth. ## Production screenshot evidence All 54 PNGs are attached to this issue: six full-page captures, 30 row crops and 18 zoom crops. They use this worktree's production SPA build and a real local API server. | Width | Light | Dark | | --- | --- | --- | | 390px phone | [light](https://git.kayg.org/attachments/e3f23b34-68d8-4d66-bf81-c3284cdc73ea) | [dark](https://git.kayg.org/attachments/93a8ab59-ad23-439a-af59-3955c6a50ccf) | | 820px tablet | [light](https://git.kayg.org/attachments/77f38df7-01bc-4928-b814-d8b0149e81bd) | [dark](https://git.kayg.org/attachments/e00ef41e-aa5d-4f89-87f0-0553ff6d2ab5) | | 1440px desktop | [light](https://git.kayg.org/attachments/1ba20b01-7fd1-40b5-b55d-b75d957204d6) | [dark](https://git.kayg.org/attachments/d2700147-6ae1-4028-96ec-aabba536ff76) | ### 3× row crops - **phone / light:** [row 01-phone-light.png](https://git.kayg.org/attachments/7f097c90-d617-4914-8795-05b99046de1e) · [row 02-phone-light.png](https://git.kayg.org/attachments/68026ac9-77b6-463a-ad09-f7ea6988f1a4) · [row 03-phone-light.png](https://git.kayg.org/attachments/17dc5ce5-3c8c-4f9f-a6a9-7e467edd6314) · [row 04-phone-light.png](https://git.kayg.org/attachments/eb4e520c-c4b3-48eb-b086-58efe34c26ec) · [row 05-phone-light.png](https://git.kayg.org/attachments/f3ca3a97-31d1-421b-81d4-47d9fd31291f) - **phone / dark:** [row 01-phone-dark.png](https://git.kayg.org/attachments/ddc612d3-f2bf-49b2-b287-c8beec72f4a8) · [row 02-phone-dark.png](https://git.kayg.org/attachments/e724a887-86c7-491f-9f2c-c0f5e6352360) · [row 03-phone-dark.png](https://git.kayg.org/attachments/3d128f69-83ef-4a29-b003-392b553f153d) · [row 04-phone-dark.png](https://git.kayg.org/attachments/e6817a69-a58f-42cd-b984-f8ce82d404c0) · [row 05-phone-dark.png](https://git.kayg.org/attachments/11e4a395-7a15-4b67-a19e-a5fd324e5d17) - **tablet / light:** [row 01-tablet-light.png](https://git.kayg.org/attachments/b6237f8a-53c0-4e43-a7a9-7f98b40bc33e) · [row 02-tablet-light.png](https://git.kayg.org/attachments/68c7635f-9809-40c6-b01b-a1f50bea97f2) · [row 03-tablet-light.png](https://git.kayg.org/attachments/3bb907a5-f2ea-4b1f-9dc8-11249e6a6c7c) · [row 04-tablet-light.png](https://git.kayg.org/attachments/101dc896-1f63-4304-9041-83144b88199f) · [row 05-tablet-light.png](https://git.kayg.org/attachments/51162b5b-2dca-4e08-aef5-e1fd6e292b4f) - **tablet / dark:** [row 01-tablet-dark.png](https://git.kayg.org/attachments/47cd2763-a3ba-47bf-9f5b-f130b410cfcc) · [row 02-tablet-dark.png](https://git.kayg.org/attachments/ebde42db-a41c-43cd-8b6f-50fe389adab1) · [row 03-tablet-dark.png](https://git.kayg.org/attachments/08e3a0ee-6bc7-4217-9560-d3218b2bf55a) · [row 04-tablet-dark.png](https://git.kayg.org/attachments/af1bba39-a6a3-43d4-a02e-c0a61fd88c1b) · [row 05-tablet-dark.png](https://git.kayg.org/attachments/5a3bba78-daaf-48e4-9544-cfa7e97f8532) - **desktop / light:** [row 01-desktop-light.png](https://git.kayg.org/attachments/b5174002-8eec-4573-aac1-9a3657b70884) · [row 02-desktop-light.png](https://git.kayg.org/attachments/63beb631-1325-472a-8571-cb7188975db6) · [row 03-desktop-light.png](https://git.kayg.org/attachments/e2206ba9-4dcd-4439-bcc1-d3328792a75d) · [row 04-desktop-light.png](https://git.kayg.org/attachments/c59b5341-964d-4ab4-be07-7e8435e77442) · [row 05-desktop-light.png](https://git.kayg.org/attachments/5638116b-d7b8-4a24-8372-d80d6cf852ef) - **desktop / dark:** [row 01-desktop-dark.png](https://git.kayg.org/attachments/4ed5df33-4106-4806-b3d0-0688d7e4d21d) · [row 02-desktop-dark.png](https://git.kayg.org/attachments/34d2770e-ff90-4744-846a-c88495c85421) · [row 03-desktop-dark.png](https://git.kayg.org/attachments/2c2bf2af-44b1-417c-94d0-b7b6551c468a) · [row 04-desktop-dark.png](https://git.kayg.org/attachments/428271d5-a88b-43aa-b041-b6eda5a0cd82) · [row 05-desktop-dark.png](https://git.kayg.org/attachments/c48da2ef-58ae-43fd-bb1f-848c14ca239d) ### Zoom crops - **phone / light:** [zoom phone-light-long.png](https://git.kayg.org/attachments/616c0e91-3b67-4012-8b5f-a89ddf9c5740) · [zoom phone-light-rtl.png](https://git.kayg.org/attachments/f8ed1f5e-b2d6-49fd-b7db-a6d86d22b04a) · [zoom phone-light-cjk.png](https://git.kayg.org/attachments/ba3f6df6-2f62-4113-800c-1a6525ba0f88) - **phone / dark:** [zoom phone-dark-long.png](https://git.kayg.org/attachments/77609394-4901-4e27-9baf-eaf85b117b12) · [zoom phone-dark-rtl.png](https://git.kayg.org/attachments/190b974b-fe90-4c43-81d4-181db61d2c6a) · [zoom phone-dark-cjk.png](https://git.kayg.org/attachments/ab17c3bc-5952-4650-b97b-10646c8cd793) - **tablet / light:** [zoom tablet-light-long.png](https://git.kayg.org/attachments/acd39707-fc71-46da-b43c-a2313d6cf565) · [zoom tablet-light-rtl.png](https://git.kayg.org/attachments/45da2aa3-e121-434b-a979-35e25da7ff73) · [zoom tablet-light-cjk.png](https://git.kayg.org/attachments/92f93807-da74-4131-b7dd-3a269ad668bc) - **tablet / dark:** [zoom tablet-dark-long.png](https://git.kayg.org/attachments/09993f67-0624-4e39-8940-6721cb5113ed) · [zoom tablet-dark-rtl.png](https://git.kayg.org/attachments/46a226a6-383b-444b-9ad1-5993e0fbb82d) · [zoom tablet-dark-cjk.png](https://git.kayg.org/attachments/ccad4d28-0780-4cdd-8404-370ffb77924f) - **desktop / light:** [zoom desktop-light-long.png](https://git.kayg.org/attachments/5c03150c-7d4b-4d43-8df9-a043cbdbc320) · [zoom desktop-light-rtl.png](https://git.kayg.org/attachments/a4d470ae-aad2-4609-81ad-7b7a618320b7) · [zoom desktop-light-cjk.png](https://git.kayg.org/attachments/97d59638-010d-419c-a981-33234cad30f1) - **desktop / dark:** [zoom desktop-dark-long.png](https://git.kayg.org/attachments/e9337f5a-998e-4fce-88d0-06b584c9f6ca) · [zoom desktop-dark-rtl.png](https://git.kayg.org/attachments/5dbd35d8-49ee-4309-8277-852716eded5f) · [zoom desktop-dark-cjk.png](https://git.kayg.org/attachments/884f0cc8-368e-4177-a093-c57b4356162d) ## Decisions - DESIGN §34 sets the filename suffix and line-count behavior. The implementation uses a balanced grapheme-safe middle cut and a 512-entry LRU keyed by width and text metrics; no per-row observer runs during scrolling. - Settings AccountRow has no shared paragraph-gap token. I used `0.5rem * --ui-scale` (8px at default scale) for the wrapped title/meta stack because the prior `0.25rem` gap looked too tight. - DESIGN §34 states the phone two-line and desktop one-line limits. I applied the one-line list limit at 820px tablet width too. - E2E used a prebuilt `agenda-413` server binary whose server, filesystem and Files plugin crates match `dev`; `CALTERNAL_E2E_ASSET_OVERRIDE=1` served this worktree's production SPA. ## Files - `apps/web/e2e/analytics.mjs`, `files.mjs`, `harness.mjs`, `photos.mjs` - `apps/web/src/lib/components/analytics/BklitAnalytics.tsx` and `analytics/vendor/bklit/charts/heatmap/heatmap-y-axis.tsx` - `apps/web/src/lib/components/app-sidebar.svelte` - `apps/web/src/lib/files/FileCollection.svelte.test.ts`, `PublicLinkPage.svelte`, `UploadToast.svelte` - `apps/web/src/lib/photos/TimelineScrubber.svelte` - `apps/web/src/lib/search/SearchResultRow.svelte` and `SearchResultRow.svelte.test.ts` - `packages/ui/src/components/calendar/ItemPreview.svelte` - `packages/ui/src/components/files/FileCollection.svelte` and `FileName.svelte` - `packages/ui/src/tokens.css` ## Gates ```text cargo fmt --check (no stdout; exit 0) bun run check svelte-check found 0 errors and 0 warnings bun run test Test Files 125 passed (125) Tests 803 passed (803) Start at 14:12:25 Duration 106.70s (transform 58%, environment 15%, import 15%, tests 9%, setup 3%) bun run build ✓ built in 31.28s Wrote site to "build" ✔ done CALTERNAL_E2E_ASSET_OVERRIDE=1 bun e2e/files.mjs --screenshots target/files-name-review --screenshots-only --shot-surface files FILES SCREENSHOTS PASSED CSP REPORTS files: 0 across 7 pages ``` No Rust source or contract changed, so Rust clippy/tests were not run. `node --check apps/web/e2e/files.mjs` and `git diff --check` also passed. ## Known gaps - The filename screenshot path uses Playwright's native row crops and passed. This host's `sharp` native module could not load `libstdc++.so.6`; the separate selection/header 2× crop path still uses `sharp` and was not part of this capture. - The branch-local server build was stopped after long shared-host contention. The compatible prebuilt server above supplied the real API during E2E. - Issue #142 is closed and its body covers Unicode join-control validation, not this UI review. I posted this report and evidence there without changing the issue state. ## Commits Head: `b65058175f7dc2578c78fe2d6098101bfb42dfc8`. Merged dev once before final gates as instructed; no push or deploy was performed.
Author
Owner

Resuming ui-small-2 on branch job/ui-small-2, based on dev at 191b179baa. The accepted filename truncation and suffix behavior remains in place. I am fixing the reported phone row rhythm, adding the requested spacing assertion, and recapturing production screenshots at 390, 820, and 1440 px in both themes.

Resuming ui-small-2 on branch job/ui-small-2, based on dev at 191b179baac3ef4f5bebfe07ce91c4b7a887ace2. The accepted filename truncation and suffix behavior remains in place. I am fixing the reported phone row rhythm, adding the requested spacing assertion, and recapturing production screenshots at 390, 820, and 1440 px in both themes.
Author
Owner

Finding and fix: the compact Files list used a fixed 60px row although phone filenames can wrap to two lines and show metadata. The metadata could overrun its row and sit close to the following title. The row is now 76px; the title-to-metadata gap matches Settings at 0.2rem.

The production Files e2e passed on the real local server. At 390px, light and dark, it asserts a 2–4px rendered text gap, metadata-to-next-title spacing at least twice that gap, and icon centring within 1px. Captures are saved in artifacts/ui-small-2/files-final/.

Finding and fix: the compact Files list used a fixed 60px row although phone filenames can wrap to two lines and show metadata. The metadata could overrun its row and sit close to the following title. The row is now 76px; the title-to-metadata gap matches Settings at 0.2rem. The production Files e2e passed on the real local server. At 390px, light and dark, it asserts a 2–4px rendered text gap, metadata-to-next-title spacing at least twice that gap, and icon centring within 1px. Captures are saved in `artifacts/ui-small-2/files-final/`.
Author
Owner

ui-small-2 final report

Built

  • Phone Files rows now keep each filename and its metadata in one tight group. The title-to-metadata gap matches Settings at 0.2rem, and the row is 76px tall so a two-line filename and metadata fit with vertical breathing room.
  • The file icon centres on the complete title-plus-metadata group.
  • The production e2e asserts a 2–4px rendered text gap, gap(title→meta) × 2 ≤ gap(meta→next title), and icon alignment within 1px on phone rows.
  • Captured full screenshots and paired-row crops at 390px, 820px and 1440px, in light and dark. All 12 requested PNGs are saved under artifacts/ui-small-2/files-final/ and attached to this issue.

Files

  • packages/ui/src/components/files/FileCollection.svelte
  • apps/web/e2e/files.mjs
  • apps/web/src/lib/components/app-sidebar.svelte — merged the dev null-safe navigation guard and combined its comments.

Production evidence

View Light Dark
390px phone full screenshot · two-row crop full screenshot · two-row crop
820px tablet full screenshot · two-row crop full screenshot · two-row crop
1440px desktop full screenshot · two-row crop full screenshot · two-row crop

Flow: register a test User with the Files e2e harness → create the Name review folder and upload API-backed filename fixtures → open /files?path=Name%20review → check filename geometry and capture screenshots. The Browser plugin was not available, so I used the repo's Playwright production-build harness. It reported FILES SCREENSHOTS PASSED and CSP REPORTS files: 0 across 7 pages.

Gates

cd apps/web && bun run check

$ node scripts/check-type-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Text sizes use shared role tokens.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/ui-small-2/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

cd apps/web && bun run test

$ vitest run

 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/ui-small-2/apps/web

Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Could not parse CSS stylesheet
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method

 Test Files  125 passed (125)
      Tests  803 passed (803)
   Start at  14:58:35
   Duration  66.97s (transform 47%, environment 22%, import 15%, tests 11%, setup 5%)

Environment  |component| jsdom was created 39 times · 84.34s total, 30% of tracked time
             create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
             learn more: https://vitest.dev/guide/improving-performance#test-environments

Merge and commits

  • Merged dev once at 70fce975945c272c7ed7f3e1844949e818ac778f. The only conflict was the sidebar null-URL guard comment; both branches had the same null-safe condition, and the merged comment keeps both explanations.
  • Feature commit: 2fb760eb7a6b4d0b0f9f4e1ae0a39fb06c9c8695 (fix(files): pair phone filenames with metadata).
  • Head SHA: 10ca061192e0af8e2d87bbd3beed82b79f5463b2.
  • No push or deploy was performed.

Known gaps

  • The dev merge added iCalendar fixtures with required CRLF line endings. git diff --cached --check reports those carriage returns as trailing whitespace; I left the fixtures unchanged.
  • The production build completed, with Rolldown's existing use client module-directive warnings.
  • The issue remains closed and its original body still describes the Unicode join-control report. I left its state unchanged.

Decision not specified by DESIGN.md

DESIGN §34 permits a two-line Files name on phones but does not set the compact row height. I used a fixed 76px row so the supported two-line name, 0.2rem Settings-matched metadata gap, caption and icon fit without per-row layout measurement. Please confirm this row height with the owner.

# ui-small-2 final report ## Built - Phone Files rows now keep each filename and its metadata in one tight group. The title-to-metadata gap matches Settings at 0.2rem, and the row is 76px tall so a two-line filename and metadata fit with vertical breathing room. - The file icon centres on the complete title-plus-metadata group. - The production e2e asserts a 2–4px rendered text gap, `gap(title→meta) × 2 ≤ gap(meta→next title)`, and icon alignment within 1px on phone rows. - Captured full screenshots and paired-row crops at 390px, 820px and 1440px, in light and dark. All 12 requested PNGs are saved under `artifacts/ui-small-2/files-final/` and attached to this issue. ## Files - `packages/ui/src/components/files/FileCollection.svelte` - `apps/web/e2e/files.mjs` - `apps/web/src/lib/components/app-sidebar.svelte` — merged the `dev` null-safe navigation guard and combined its comments. ## Production evidence | View | Light | Dark | | --- | --- | --- | | 390px phone | [full screenshot](https://git.kayg.org/attachments/0c8e6828-8792-4624-9c51-723f1f7f8f4d) · [two-row crop](https://git.kayg.org/attachments/72bd03e8-0872-4d4b-bdc2-c689d549f93c) | [full screenshot](https://git.kayg.org/attachments/249b8108-65ff-4b85-9bc8-81e9d49f1341) · [two-row crop](https://git.kayg.org/attachments/5b889597-37ac-4fb8-a9b0-b7c2f2c4d4c0) | | 820px tablet | [full screenshot](https://git.kayg.org/attachments/f656004f-2693-402c-bc8a-ff9f7e62eef0) · [two-row crop](https://git.kayg.org/attachments/5e534869-0ec0-4d53-bdcd-baff218246b2) | [full screenshot](https://git.kayg.org/attachments/3e324bd1-0c58-4da5-86ac-d89f2b2b8b75) · [two-row crop](https://git.kayg.org/attachments/29a2b413-e809-461a-9ddc-c331c968776d) | | 1440px desktop | [full screenshot](https://git.kayg.org/attachments/d83a2ba2-5546-4332-a428-3599397c7a61) · [two-row crop](https://git.kayg.org/attachments/5789a82a-491e-4663-a58e-35d9b57ac773) | [full screenshot](https://git.kayg.org/attachments/780e8f06-da6f-476f-b6a7-dc531c53ba3e) · [two-row crop](https://git.kayg.org/attachments/28a20ba4-3187-4cac-bb4f-66acf3fa8151) | Flow: register a test User with the Files e2e harness → create the `Name review` folder and upload API-backed filename fixtures → open `/files?path=Name%20review` → check filename geometry and capture screenshots. The Browser plugin was not available, so I used the repo's Playwright production-build harness. It reported `FILES SCREENSHOTS PASSED` and `CSP REPORTS files: 0 across 7 pages`. ## Gates `cd apps/web && bun run check` ```text $ node scripts/check-type-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Text sizes use shared role tokens. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/ui-small-2/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `cd apps/web && bun run test` ```text $ vitest run RUN v5.0.1 /home/kayg/Developer/calternal-wt/ui-small-2/apps/web Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Could not parse CSS stylesheet Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Test Files 125 passed (125) Tests 803 passed (803) Start at 14:58:35 Duration 66.97s (transform 47%, environment 22%, import 15%, tests 11%, setup 5%) Environment |component| jsdom was created 39 times · 84.34s total, 30% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments ``` ## Merge and commits - Merged `dev` once at `70fce975945c272c7ed7f3e1844949e818ac778f`. The only conflict was the sidebar null-URL guard comment; both branches had the same null-safe condition, and the merged comment keeps both explanations. - Feature commit: `2fb760eb7a6b4d0b0f9f4e1ae0a39fb06c9c8695` (`fix(files): pair phone filenames with metadata`). - Head SHA: `10ca061192e0af8e2d87bbd3beed82b79f5463b2`. - No push or deploy was performed. ## Known gaps - The `dev` merge added iCalendar fixtures with required CRLF line endings. `git diff --cached --check` reports those carriage returns as trailing whitespace; I left the fixtures unchanged. - The production build completed, with Rolldown's existing `use client` module-directive warnings. - The issue remains closed and its original body still describes the Unicode join-control report. I left its state unchanged. ## Decision not specified by DESIGN.md DESIGN §34 permits a two-line Files name on phones but does not set the compact row height. I used a fixed 76px row so the supported two-line name, 0.2rem Settings-matched metadata gap, caption and icon fit without per-row layout measurement. Please confirm this row height with the owner.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#142
No description provided.