File browser redesign (Claude Design): visual + multi-select + grid + owner/chips #311

Closed
opened 2026-08-25 20:27:33 +00:00 by Cordy · 9 comments
Owner

Port the Claude Design file-browser redesign (design_handoff_file_browser/: README + files-mockup.html) into web/static/index.html, functionally and visually. High-fidelity handoff; exact tokens/spacing/states specified.

Status

  • PR1 — SHIPPED. Merged as #312, tagged v0.6.64, image 192.168.10.245/cordy/cairn:v0.6.64@sha256:932aa2af…, live on the dogfood (cairn-enc, 192.168.10.249, pod healthy 1/1, no restarts). Visual confirmation still pending an operator login (password entry is out of scope for the agent).
  • PR2 / PR3 — deferred by design. They change the core file-listing API (GET /api/v1/files), which can't be integration-tested from CI alone; recommended to do with the operator available. Not started autonomously.

Key finding (shapes the sequencing)

Against the current code this is more than a restyle. The file-listing API (GET /api/v1/files) returns only name / size / modTime / path / isDir per item. The mockup depicts features/data we don't have yet:

  • Multi-select + bulk actions (selection bar: Download / Share / Move / Delete) — no selection today.
  • Grid view (list/grid toggle) — list only today.
  • Owner column and status chips (shared / legal-hold / lock / public-link) — no owner or per-item status in the listing; and folder item counts for the name sub-line.
  • Sidebar storage meter (data already exists in the user menu — portable).

Also: current token system is --snow/--surface/--slate-*/--lichen/--glacier; the mockup introduces --paper/--ink/--accent/--rule/--sunk. The redesign is scoped to the file browser, so the new tokens must coexist with the old ones (still used by preview/admin/dialog panels). Accents already exist as a pref (glacier default + moss/ember/graphite); the mockup's default is moss and adds a 4th (glacier as a named accent) — reconcile at port time.

Plan (incremental PRs)

  • PR1 — Frontend foundation + client-only features. New token/theme integration (keep old tokens), restyle header→.top / sidebar→.side+navscroll+quota storage meter / toolbar / breadcrumb / empty state; rewrite renderRows (~1774) from <table> to CSS-grid .lr rows; add multi-select (a Set + single applySel writer, survives sort & list↔grid) with the selection bar wired to existing single-item ops; add grid view toggle. Columns for now: name / size / modified / actions (owner + chips hidden until PR2/3). No backend change. (shipped: #312, v0.6.64)
  • PR2 — Backend: extend GET /api/v1/files. Add per-item owner, itemCount (dirs), and status flags shared / link / hold / lock, sourced from the existing shares/holds/locks subsystems. Keep it cheap (batch lookups, not N+1). (deferred — do with operator available)
  • PR3 — Owner column + status chips. Consume PR2 fields: owner cell, chips on the name line, folder item-counts in the name sub-line. Personal space hides Owner (per handoff). (deferred — depends on PR2)
  • Optional — Ebony theme. applyPrefs 3-value theme + Appearance-dialog third option; CSS block already in the port.

Constraints (from the handoff)

  • Zero-build, single-file, vanilla JS. No framework, no bundler, no webfont CDN (system stack; vendor Libre Franklin woff2 locally if we want it). Keep every id, data-i18n key, event binding, and localStorage["cairn-prefs"] value.
  • Icon system (svgIcon/ICON_FOLDER/FILE_BASE/ICONS/EXT_GROUPS/iconFor) reused verbatim.
  • Node --check + Go build/test as the CI gate; PRs for review (no auto-ship to the dogfood).

Design handoff files live in the operator's design_handoff_file_browser/ folder (not committed). Working branch: feat/files-redesign.

Port the Claude Design file-browser redesign (`design_handoff_file_browser/`: README + `files-mockup.html`) into `web/static/index.html`, **functionally and visually**. High-fidelity handoff; exact tokens/spacing/states specified. ## Status - **PR1 — SHIPPED.** Merged as #312, tagged **v0.6.64**, image `192.168.10.245/cordy/cairn:v0.6.64@sha256:932aa2af…`, live on the dogfood (`cairn-enc`, 192.168.10.249, pod healthy 1/1, no restarts). Visual confirmation still pending an operator login (password entry is out of scope for the agent). - **PR2 / PR3 — deferred by design.** They change the core file-listing API (`GET /api/v1/files`), which can't be integration-tested from CI alone; recommended to do with the operator available. Not started autonomously. ## Key finding (shapes the sequencing) Against the current code this is more than a restyle. The file-listing API (`GET /api/v1/files`) returns only `name / size / modTime / path / isDir` per item. The mockup depicts features/data we don't have yet: - **Multi-select + bulk actions** (selection bar: Download / Share / Move / Delete) — no selection today. - **Grid view** (list/grid toggle) — list only today. - **Owner column** and **status chips** (shared / legal-hold / lock / public-link) — no owner or per-item status in the listing; and folder **item counts** for the name sub-line. - Sidebar **storage meter** (data already exists in the user menu — portable). Also: current token system is `--snow/--surface/--slate-*/--lichen/--glacier`; the mockup introduces `--paper/--ink/--accent/--rule/--sunk`. The redesign is scoped to the file browser, so the new tokens must coexist with the old ones (still used by preview/admin/dialog panels). Accents already exist as a pref (glacier default + moss/ember/graphite); the mockup's default is moss and adds a 4th (glacier as a named accent) — reconcile at port time. ## Plan (incremental PRs) - [x] **PR1 — Frontend foundation + client-only features.** New token/theme integration (keep old tokens), restyle header→`.top` / sidebar→`.side`+`navscroll`+`quota` storage meter / toolbar / breadcrumb / empty state; rewrite `renderRows` (~1774) from `<table>` to CSS-grid `.lr` rows; add **multi-select** (a `Set` + single `applySel` writer, survives sort & list↔grid) with the selection bar wired to existing single-item ops; add **grid view** toggle. Columns for now: name / size / modified / actions (owner + chips hidden until PR2/3). No backend change. **(shipped: #312, v0.6.64)** - [ ] **PR2 — Backend: extend `GET /api/v1/files`.** Add per-item `owner`, `itemCount` (dirs), and status flags `shared` / `link` / `hold` / `lock`, sourced from the existing shares/holds/locks subsystems. Keep it cheap (batch lookups, not N+1). *(deferred — do with operator available)* - [ ] **PR3 — Owner column + status chips.** Consume PR2 fields: owner cell, chips on the name line, folder item-counts in the name sub-line. Personal space hides Owner (per handoff). *(deferred — depends on PR2)* - [x] **Optional — Ebony theme.** `applyPrefs` 3-value theme + Appearance-dialog third option; CSS block already in the port. ## Constraints (from the handoff) - Zero-build, single-file, vanilla JS. No framework, no bundler, **no webfont CDN** (system stack; vendor Libre Franklin woff2 locally if we want it). Keep every id, `data-i18n` key, event binding, and `localStorage["cairn-prefs"]` value. - Icon system (`svgIcon/ICON_FOLDER/FILE_BASE/ICONS/EXT_GROUPS/iconFor`) reused verbatim. - Node `--check` + Go build/test as the CI gate; PRs for review (no auto-ship to the dogfood). Design handoff files live in the operator's `design_handoff_file_browser/` folder (not committed). Working branch: `feat/files-redesign`.
Author
Owner

Phase 2 shipped — v0.6.65 (PR #313, squash-merged)

Live on the dogfood: cairn-enc on 192.168.10.245/cordy/cairn:v0.6.65@sha256:db68c142…, pod Ready 1/1, 0 restarts, /healthz green.

PR1 gaps the operator flagged

  • Nav group labels — Shared and This instance sub-headers (.navgrp), each auto-hiding when all its items are hidden.
  • Ebony theme — the missing appearance option. black value end-to-end: Appearance-dialog option, applyPrefs() promoted to tri-state, new-token palette + per-accent overrides (handoff values), plus an old-token fallback block so admin/preview/dialog surfaces stay dark. (The accent picker + moss/ember/graphite already existed and worked — Ebony was the one genuinely-missing option.)

PR2 — GET /api/v1/files enrichment (internal/api/listmeta.go)

  • shared — caller has an active public link on the path.
  • hold — a legal hold covers the path/ancestor.
  • Both share and hold state persist in backend-path space, so each item is resolved via the scope driver's pure Resolve before matching (no I/O). Best-effort; a disabled subsystem or unresolvable path just yields no flag.
  • Deliberately dropped after reading the data model: owner (storage.FileInfo has no uploader — files carry no creator metadata), lock (the only lock is the instance boot-guard in statestore, not per-file), itemCount (a List per subdir = N+1 on the main-screen hot path). Fabricating owner/lock in a product with real legal-hold semantics would be worse than omitting them. The owner column therefore stays hidden; making it real needs a storage-layer schema change, not a UI change.

PR3 — status chips

  • Name line renders Shared + On hold chips from the PR2 fields. Owner column remains hidden (no data).

Gate: anchored splices (assert count==1), go vet ./... clean, go test ./internal/api/... green (new applyFlags tests), go build ./cmd/cairnd OK, node --check OK, and CI (ci.yml) green on the PR head.

Left for the operator (bulk visual check, needs a fresh login — in-memory sessions): nav group labels, the Ebony theme option, and the chips on shared/held files. Also worth an eyeball: whether the file-nav buttons correctly hide in admin mode (a pre-existing PR1 structural question, not touched here).

### Phase 2 shipped — v0.6.65 (PR #313, squash-merged) Live on the dogfood: `cairn-enc` on `192.168.10.245/cordy/cairn:v0.6.65@sha256:db68c142…`, pod Ready 1/1, 0 restarts, `/healthz` green. **PR1 gaps the operator flagged** - [x] Nav group labels — `Shared` and `This instance` sub-headers (`.navgrp`), each auto-hiding when all its items are hidden. - [x] Ebony theme — the missing appearance option. `black` value end-to-end: Appearance-dialog option, `applyPrefs()` promoted to tri-state, new-token palette + per-accent overrides (handoff values), plus an old-token fallback block so admin/preview/dialog surfaces stay dark. (The accent picker + moss/ember/graphite already existed and worked — Ebony was the one genuinely-missing option.) **PR2 — `GET /api/v1/files` enrichment (`internal/api/listmeta.go`)** - [x] `shared` — caller has an active public link on the path. - [x] `hold` — a legal hold covers the path/ancestor. - Both share and hold state persist in **backend-path** space, so each item is resolved via the scope driver's pure `Resolve` before matching (no I/O). Best-effort; a disabled subsystem or unresolvable path just yields no flag. - **Deliberately dropped after reading the data model:** `owner` (`storage.FileInfo` has no uploader — files carry no creator metadata), `lock` (the only lock is the instance boot-guard in `statestore`, not per-file), `itemCount` (a `List` per subdir = N+1 on the main-screen hot path). Fabricating owner/lock in a product with real legal-hold semantics would be worse than omitting them. The owner **column** therefore stays hidden; making it real needs a storage-layer schema change, not a UI change. **PR3 — status chips** - [x] Name line renders `Shared` + `On hold` chips from the PR2 fields. Owner column remains hidden (no data). **Gate:** anchored splices (assert count==1), `go vet ./...` clean, `go test ./internal/api/...` green (new `applyFlags` tests), `go build ./cmd/cairnd` OK, `node --check` OK, and CI (`ci.yml`) green on the PR head. **Left for the operator (bulk visual check, needs a fresh login — in-memory sessions):** nav group labels, the Ebony theme option, and the chips on shared/held files. Also worth an eyeball: whether the file-nav buttons correctly hide in admin mode (a pre-existing PR1 structural question, not touched here).
Author
Owner

Follow-up fix shipped — v0.6.66 (PR #314)

Members button missing in spaces. Root cause was a PR1 stopgap, not the redesign logic: PR1 added document.body.setAttribute("data-space", "personal") at init (to keep the empty Owner column hidden "until PR2/PR3") and never updates it per-view. That made the CSS rule body[data-space="personal"] #spacemembers{display:none} fire in every view, hiding the button even inside a space. The button's JS gate (feat.spaceMembers && at a /spaces/<name> root) was byte-identical before PR1, in PR1, and now — so before PR1 (no data-space attribute) the rule never matched and the button showed.

Fix: dropped the redundant #spacemembers CSS rule; the JS gate alone hides it correctly in personal space and shows it in a space. Owner column stays hidden (the data-space="personal" default is left in place intentionally, since there's still no per-item owner data). CI green on the head commit; live on the dogfood as v0.6.66.

### Follow-up fix shipped — v0.6.66 (PR #314) **Members button missing in spaces.** Root cause was a PR1 stopgap, not the redesign logic: PR1 added `document.body.setAttribute("data-space", "personal")` at init (to keep the empty Owner column hidden "until PR2/PR3") and never updates it per-view. That made the CSS rule `body[data-space="personal"] #spacemembers{display:none}` fire in *every* view, hiding the button even inside a space. The button's JS gate (`feat.spaceMembers && at a /spaces/<name> root`) was byte-identical before PR1, in PR1, and now — so before PR1 (no `data-space` attribute) the rule never matched and the button showed. Fix: dropped the redundant `#spacemembers` CSS rule; the JS gate alone hides it correctly in personal space and shows it in a space. Owner column stays hidden (the `data-space="personal"` default is left in place intentionally, since there's still no per-item owner data). CI green on the head commit; live on the dogfood as v0.6.66.
Author
Owner

Redesign phase 3 is shipped and live on both dogfoods (v0.6.75), ported from the new files-mockup.html handoff in five slices:

Slice PR Release
Five-theme system + visual Appearance dialog #320 v0.6.71
Identity-first user menu + show-once app-password panel #321 v0.6.72
Notifications panel (grouped, directional, unread-aware) #322 v0.6.73
Members dialog (owner row, avatar list, footer Close) #323 v0.6.74
Preview panel behaviour + container-query columns #324 v0.6.75

Themes are now five, per the operator's call: Light, Night (the existing blue dark — stored value stays dark so no saved preference orphans), Dusk (new, the mockup's green-cast dark), Ebony, Slate (new). Both token systems (--paper/--ink/--accent and the legacy --snow/--glacier) carry all of them plus every accent variant, so legacy panels theme along.

Two real bugs surfaced while porting, both fixed:

  • The previewed-row marker (pvsel) was still styled as tr.pvsel > td — dead since the table became .lr grid rows, so "you are previewing this" has rendered nothing in the shipped app.
  • Column drops were @media-keyed, but the preview panel takes width away without the viewport changing: the breakpoints never fired and the shortfall landed entirely on the filename track. Now @container list.

The pointer-capture divider supersedes the v0.6.67 iframe workaround (agreed with the operator); that patch is removed.

Not ported / deliberately open:

  • The mockup's review chrome (theme/state switcher bar) is excluded by design — it exists to simulate server states.
  • #membersdlg-idp (IdP-managed membership notice) ships hidden with i18n, waiting on a backend flag — that's #129.
  • Per-item unread state in notifications is approximated from the API's unread count (newest-N marked); exact per-item read state needs a backend change.
  • Libre Franklin is still the system-stack approximation; vendoring the woff2 locally remains open (no CDN, per the handoff).
  • Grid-view tiles, empty state and selection bar were already in place from phases 1–2 and were not re-touched beyond the marker work.

Fidelity spot-check across the five themes and the four accents is the operator's visual pass on the dogfood.

Redesign phase 3 is shipped and live on both dogfoods (**v0.6.75**), ported from the new `files-mockup.html` handoff in five slices: | Slice | PR | Release | |---|---|---| | Five-theme system + visual Appearance dialog | #320 | v0.6.71 | | Identity-first user menu + show-once app-password panel | #321 | v0.6.72 | | Notifications panel (grouped, directional, unread-aware) | #322 | v0.6.73 | | Members dialog (owner row, avatar list, footer Close) | #323 | v0.6.74 | | Preview panel behaviour + container-query columns | #324 | v0.6.75 | **Themes are now five**, per the operator's call: Light, **Night** (the existing blue dark — stored value stays `dark` so no saved preference orphans), **Dusk** (new, the mockup's green-cast dark), **Ebony**, **Slate** (new). Both token systems (`--paper/--ink/--accent` and the legacy `--snow/--glacier`) carry all of them plus every accent variant, so legacy panels theme along. **Two real bugs surfaced while porting**, both fixed: - The previewed-row marker (`pvsel`) was still styled as `tr.pvsel > td` — dead since the table became `.lr` grid rows, so "you are previewing this" has rendered nothing in the shipped app. - Column drops were `@media`-keyed, but the preview panel takes width away *without the viewport changing*: the breakpoints never fired and the shortfall landed entirely on the filename track. Now `@container list`. The pointer-capture divider **supersedes the v0.6.67 iframe workaround** (agreed with the operator); that patch is removed. **Not ported / deliberately open:** - The mockup's review chrome (theme/state switcher bar) is excluded by design — it exists to simulate server states. - `#membersdlg-idp` (IdP-managed membership notice) ships hidden with i18n, waiting on a backend flag — that's #129. - Per-item unread state in notifications is approximated from the API's unread **count** (newest-N marked); exact per-item read state needs a backend change. - Libre Franklin is still the system-stack approximation; vendoring the woff2 locally remains open (no CDN, per the handoff). - Grid-view tiles, empty state and selection bar were already in place from phases 1–2 and were not re-touched beyond the marker work. Fidelity spot-check across the five themes and the four accents is the operator's visual pass on the dogfood.
Author
Owner

Admin redesign slice A shipped — v0.6.77, live on both dogfoods. PR #326.

Rail regroups to three (Instance / Access, sharing & federation / Billing — operator's call: Federation merges into access and sharing rather than standing alone, Billing keeps its own group). "← Files" leaves the rail and becomes "Exit to files" in a labelled header band with a crumb and a height-reserved description. Overview drops three equal stat cards for two meters (the only quantities with denominators — storage, seats) plus grouped status rows on the shared chip component.

Remaining admin slices: B settings-row pattern + switches + save bar + segmented tabs (Basic/Storage/Sharing/Users/Encryption) · C data-heavy topics (Deleted files, Audit log, Spaces inventory) · D Retention & holds, Updates, Licensing, Peering.


Deferred items — running list

To be swept in one pass once the UI port is complete. Numbering continues across the whole redesign.

# Item Blocked on / nature
1 IdP-managed members notice (#membersdlg-idp ships hidden, i18n done) backend flag — #129
2 Per-item unread state in notifications (newest-N approximated from the API's unread count) backend change
3 Vendor Libre Franklin woff2 locally (no CDN, per the handoff) asset work
4 Rail inbox dot element + its JS still present, only CSS-hidden cleanup
5 Structure-coupled selector sweep — tr.pvsel and aside > button were both silently orphaned by the redesign; likely more audit pass
6 Admin Overview facts with no API behind them: audit event count, active public-link count, retention floor, storage driver name API surface
7 Overview attention strip (.notice, "an update is available") — needs the Updates check to run on Overview lands with slice D
8 Dead CSS from slice A: .ovcard/.ovcards/.ovlabel/.ovnum, possibly .kvbadge cleanup, fold into #5

Items 6 and 7 are the ones that need a product decision rather than just cleanup: either the endpoints grow to serve them, or those Overview rows stay out. I've omitted rather than faked them.

**Admin redesign slice A shipped — v0.6.77, live on both dogfoods.** PR #326. Rail regroups to three (Instance / **Access, sharing & federation** / Billing — operator's call: Federation merges into access and sharing rather than standing alone, Billing keeps its own group). "← Files" leaves the rail and becomes "Exit to files" in a labelled header band with a crumb and a height-reserved description. Overview drops three equal stat cards for two meters (the only quantities with denominators — storage, seats) plus grouped status rows on the shared `chip` component. Remaining admin slices: **B** settings-row pattern + switches + save bar + segmented tabs (Basic/Storage/Sharing/Users/Encryption) · **C** data-heavy topics (Deleted files, Audit log, Spaces inventory) · **D** Retention & holds, Updates, Licensing, Peering. --- ## Deferred items — running list To be swept in one pass once the UI port is complete. Numbering continues across the whole redesign. | # | Item | Blocked on / nature | |---|---|---| | 1 | IdP-managed members notice (`#membersdlg-idp` ships hidden, i18n done) | backend flag — #129 | | 2 | Per-item unread state in notifications (newest-N approximated from the API's unread **count**) | backend change | | 3 | Vendor Libre Franklin woff2 locally (no CDN, per the handoff) | asset work | | 4 | Rail inbox dot element + its JS still present, only CSS-hidden | cleanup | | 5 | Structure-coupled selector sweep — `tr.pvsel` and `aside > button` were both silently orphaned by the redesign; likely more | audit pass | | 6 | Admin Overview facts with no API behind them: audit event count, active public-link count, retention floor, storage driver name | API surface | | 7 | Overview attention strip (`.notice`, "an update is available") — needs the Updates check to run on Overview | lands with slice D | | 8 | Dead CSS from slice A: `.ovcard/.ovcards/.ovlabel/.ovnum`, possibly `.kvbadge` | cleanup, fold into #5 | Items 6 and 7 are the ones that need a product decision rather than just cleanup: either the endpoints grow to serve them, or those Overview rows stay out. I've omitted rather than faked them.
Author
Owner

Admin slice B shipped — v0.6.78, live on both dogfoods. PR #327.

Settings rows (name + explanation left, control right), checkboxes render as switches, underline tabs become a segmented control, and Save becomes a sticky bar that appears only when something changed, counts the pending changes and offers Discard.

Implementation note worth keeping: this is presentation only. SET_SLOTS still relocates the same field elements, every field id / value binding / save handler is untouched, and all the CSS is scoped to #adminview so the settings dialog and other .field / .dlgactions users are unaffected. The save bar is selected with :has(button[id^="adm-save-"]) so the Updates topic's "Check for updates" bar is not swallowed. Baseline for the dirty state is captured right after loadSettingsValues() and re-captured after a successful save by wrapping the existing onclick.

Remaining: C data-heavy topics (Deleted files, Audit log, Spaces inventory) · D Retention & holds, Updates, Licensing, Peering.


Deferred items — running list (updated)

# Item Blocked on / nature
1 IdP-managed members notice (#membersdlg-idp ships hidden, i18n done) backend flag — #129
2 Per-item unread state in notifications (newest-N approximated from a count) backend change
3 Vendor Libre Franklin woff2 locally (no CDN) asset work
4 Rail inbox dot element + JS still present, only CSS-hidden cleanup
5 Structure-coupled selector sweep (tr.pvsel, aside > button class of bug) audit pass
6 Admin Overview facts with no API: audit event count, active link count, retention floor, storage driver name API surface
7 Overview attention strip (.notice) — needs the Updates check on Overview lands with slice D
8 Dead CSS: .ovcard/.ovcards/.ovlabel/.ovnum, possibly .kvbadge cleanup, fold into #5
9 Ten new setting descriptions are English-only — they're new copy, and four invented translations would be worse than one honest string translation pass
10 Default share expiry stays in Basic settings. The mockup moves it to Sharing (next to the maximum the server enforces) with a pointer panel left behind — a real control relocation, not styling product decision
11 Settings without a description in SET_DESC render without one rather than getting invented copy — the map covers what I could name with confidence content pass

Items 6, 7 and 10 need a product decision; the rest are cleanup or content.

**Admin slice B shipped — v0.6.78, live on both dogfoods.** PR #327. Settings rows (name + explanation left, control right), checkboxes render as switches, underline tabs become a segmented control, and Save becomes a sticky bar that appears only when something changed, counts the pending changes and offers Discard. Implementation note worth keeping: this is **presentation only**. `SET_SLOTS` still relocates the same field elements, every field id / value binding / save handler is untouched, and all the CSS is scoped to `#adminview` so the settings dialog and other `.field` / `.dlgactions` users are unaffected. The save bar is selected with `:has(button[id^="adm-save-"])` so the Updates topic's "Check for updates" bar is not swallowed. Baseline for the dirty state is captured right after `loadSettingsValues()` and re-captured after a successful save by wrapping the existing `onclick`. Remaining: **C** data-heavy topics (Deleted files, Audit log, Spaces inventory) · **D** Retention & holds, Updates, Licensing, Peering. --- ## Deferred items — running list (updated) | # | Item | Blocked on / nature | |---|---|---| | 1 | IdP-managed members notice (`#membersdlg-idp` ships hidden, i18n done) | backend flag — #129 | | 2 | Per-item unread state in notifications (newest-N approximated from a count) | backend change | | 3 | Vendor Libre Franklin woff2 locally (no CDN) | asset work | | 4 | Rail inbox dot element + JS still present, only CSS-hidden | cleanup | | 5 | Structure-coupled selector sweep (`tr.pvsel`, `aside > button` class of bug) | audit pass | | 6 | Admin Overview facts with no API: audit event count, active link count, retention floor, storage driver name | API surface | | 7 | Overview attention strip (`.notice`) — needs the Updates check on Overview | lands with slice D | | 8 | Dead CSS: `.ovcard/.ovcards/.ovlabel/.ovnum`, possibly `.kvbadge` | cleanup, fold into #5 | | **9** | **Ten new setting descriptions are English-only** — they're new copy, and four invented translations would be worse than one honest string | translation pass | | **10** | **Default share expiry stays in Basic settings.** The mockup moves it to Sharing (next to the maximum the server enforces) with a pointer panel left behind — a real control relocation, not styling | product decision | | **11** | Settings without a description in `SET_DESC` render without one rather than getting invented copy — the map covers what I could name with confidence | content pass | Items 6, 7 and 10 need a product decision; the rest are cleanup or content.
Author
Owner

Admin slice C shipped — v0.6.79, live on both dogfoods. PR #328.

Deleted files split into Eligible now / Protected with each protected row carrying its reason; audit log becomes an aligned time/actor/action/path grid with an action filter and a stated 200-event cap; the spaces hint paragraph becomes an inventory that surfaces deactivated spaces still holding storage.

Two findings worth recording:

  • The trash API already reported eligible, hold and keepUntil per item. Eligibility was never missing data — it was only ever rendered as a disabled button. No backend change was needed for the split.
  • A full i18n key audit (465 defined vs 251 used) caught ownerLbl undefined before it shipped; t() returns the key verbatim when missing, so the spaces list would have read "ownerLbl nikola". Now defined in all four languages. This check is worth keeping as a CI step — added as item 12 below.

Remaining: D — Retention & holds (the resolver), Updates (version comparison), Licensing (seat meter with the free-tier line), Peering (two-halves cards). That closes the admin port.


Deferred items — running list (updated)

# Item Blocked on / nature
1 IdP-managed members notice (#membersdlg-idp ships hidden, i18n done) backend flag — #129
2 Per-item unread state in notifications (newest-N approximated from a count) backend change
3 Vendor Libre Franklin woff2 locally (no CDN) asset work
4 Rail inbox dot element + JS still present, only CSS-hidden cleanup
5 Structure-coupled selector sweep (tr.pvsel, aside > button class of bug) audit pass
6 Admin Overview facts with no API: audit event count, active link count, retention floor, storage driver name API surface
7 Overview attention strip (.notice) — needs the Updates check on Overview lands with slice D
8 Dead CSS: .ovcard/.ovcards/.ovlabel/.ovnum, possibly .kvbadge; now also fmtEvt is near-unused cleanup, fold into #5
9 Ten setting descriptions are English-only translation pass
10 Default share expiry stays in Basic settings; mockup moves it to Sharing product decision
11 Settings without a SET_DESC entry render without a description content pass
12 Add the i18n key audit to CI — it caught a real verbatim-key bug this slice and costs one grep CI
13 Spaces row actions (Details / Deactivate / Reactivate / Delete with name confirmation) are not implemented — the admin API for them is unconfirmed, so rows are read-only rather than guessing API confirmation
14 Audit date-range filters (From / To) omitted — the endpoint's support for date params is unconfirmed; actor, path prefix and action are wired because they demonstrably work API confirmation

Items 6, 7, 10, 13 and 14 need a product or API decision; the rest are cleanup, content or CI.

**Admin slice C shipped — v0.6.79, live on both dogfoods.** PR #328. Deleted files split into **Eligible now** / **Protected** with each protected row carrying its reason; audit log becomes an aligned time/actor/action/path grid with an action filter and a stated 200-event cap; the spaces hint paragraph becomes an inventory that surfaces deactivated spaces still holding storage. Two findings worth recording: - **The trash API already reported `eligible`, `hold` and `keepUntil` per item.** Eligibility was never missing data — it was only ever rendered as a disabled button. No backend change was needed for the split. - **A full i18n key audit** (465 defined vs 251 used) caught `ownerLbl` undefined before it shipped; `t()` returns the key verbatim when missing, so the spaces list would have read "ownerLbl nikola". Now defined in all four languages. **This check is worth keeping as a CI step** — added as item 12 below. Remaining: **D** — Retention & holds (the resolver), Updates (version comparison), Licensing (seat meter with the free-tier line), Peering (two-halves cards). That closes the admin port. --- ## Deferred items — running list (updated) | # | Item | Blocked on / nature | |---|---|---| | 1 | IdP-managed members notice (`#membersdlg-idp` ships hidden, i18n done) | backend flag — #129 | | 2 | Per-item unread state in notifications (newest-N approximated from a count) | backend change | | 3 | Vendor Libre Franklin woff2 locally (no CDN) | asset work | | 4 | Rail inbox dot element + JS still present, only CSS-hidden | cleanup | | 5 | Structure-coupled selector sweep (`tr.pvsel`, `aside > button` class of bug) | audit pass | | 6 | Admin Overview facts with no API: audit event count, active link count, retention floor, storage driver name | API surface | | 7 | Overview attention strip (`.notice`) — needs the Updates check on Overview | lands with slice D | | 8 | Dead CSS: `.ovcard/.ovcards/.ovlabel/.ovnum`, possibly `.kvbadge`; now also `fmtEvt` is near-unused | cleanup, fold into #5 | | 9 | Ten setting descriptions are English-only | translation pass | | 10 | Default share expiry stays in Basic settings; mockup moves it to Sharing | product decision | | 11 | Settings without a `SET_DESC` entry render without a description | content pass | | **12** | **Add the i18n key audit to CI** — it caught a real verbatim-key bug this slice and costs one grep | CI | | **13** | **Spaces row actions** (Details / Deactivate / Reactivate / Delete with name confirmation) are not implemented — the admin API for them is unconfirmed, so rows are read-only rather than guessing | API confirmation | | **14** | **Audit date-range filters** (From / To) omitted — the endpoint's support for date params is unconfirmed; actor, path prefix and action are wired because they demonstrably work | API confirmation | Items 6, 7, 10, 13 and 14 need a product or API decision; the rest are cleanup, content or CI.
Author
Owner

Slice D shipped (v0.6.80, #329) — the admin port is complete. Updates, legal holds and licensing done; peering deliberately untouched.

Three new deferred items, plus one correction to the plan:

# Item Why it was not done now
15 Retention rules table — render the rules textarea as structured rows Rules are edited as free text. Rendering them as rows means parsing a syntax I have not verified, and a parser that silently mis-reads a rule is worse than a textarea that shows it plainly. Needs the grammar confirmed against the server's parser first.
16 Retention path resolver — "what applies to this path?" Would have to resolve client-side from the rules text plus holds, and could disagree with the server's own answer. There is no resolve endpoint to ask. Wants a small GET /api/v1/admin/retention/resolve?path= that returns the effective rule and its source; then the UI just displays the server's answer instead of computing a rival one.
17 Peering: none needed — not deferred, closed as already done peerCardEl already joins the inbound key we issued with the outbound peer entry and handles either half being absent (v0.6.32–41). The mockup's "two halves" card is what is already shipping. Restyling would have re-litigated a design that already landed.

Process note for the one-shot house style (not a product item, but it cost four failed runs across slices C and D): the hand-written grep -c … ; test "$X" = "N" verification block adds nothing over the splices' own assert count == 1 plus node --check, and every one of its four failures was my count being wrong while the edit itself was correct — the file was written, then the job aborted before pushing. Causes seen: element created via className in JS so the class string appears once not twice; a symbol legitimately appearing 2× not 3×. The last workflow drops the greps entirely and keeps node --check + the i18n key audit, which are the two checks that can actually fail for a real reason. Recommend that as the standing pattern.

Item 12 (add the i18n key audit to ci.yml) is now more clearly worth doing: it has caught one real bug (ownerLbl, slice C) and passed cleanly on every slice since, so it is cheap and it works. It only runs because I paste it into each one-shot; in CI it would guard every contributor's change.

**Slice D shipped (v0.6.80, #329) — the admin port is complete.** Updates, legal holds and licensing done; peering deliberately untouched. Three new deferred items, plus one correction to the plan: | # | Item | Why it was not done now | |---|---|---| | 15 | **Retention rules table** — render the rules textarea as structured rows | Rules are edited as free text. Rendering them as rows means parsing a syntax I have not verified, and a parser that silently mis-reads a rule is worse than a textarea that shows it plainly. Needs the grammar confirmed against the server's parser first. | | 16 | **Retention path resolver** — "what applies to this path?" | Would have to resolve client-side from the rules text plus holds, and could disagree with the server's own answer. There is no resolve endpoint to ask. Wants a small `GET /api/v1/admin/retention/resolve?path=` that returns the effective rule and its source; then the UI just displays the server's answer instead of computing a rival one. | | 17 | **Peering: none needed** — not deferred, *closed as already done* | `peerCardEl` already joins the inbound key we issued with the outbound peer entry and handles either half being absent (v0.6.32–41). The mockup's "two halves" card is what is already shipping. Restyling would have re-litigated a design that already landed. | **Process note for the one-shot house style** (not a product item, but it cost four failed runs across slices C and D): the hand-written `grep -c … ; test "$X" = "N"` verification block adds nothing over the splices' own `assert count == 1` plus `node --check`, and every one of its four failures was *my* count being wrong while the edit itself was correct — the file was written, then the job aborted before pushing. Causes seen: element created via `className` in JS so the class string appears once not twice; a symbol legitimately appearing 2× not 3×. The last workflow drops the greps entirely and keeps `node --check` + the i18n key audit, which are the two checks that can actually fail for a real reason. Recommend that as the standing pattern. Item 12 (add the i18n key audit to `ci.yml`) is now more clearly worth doing: it has caught one real bug (`ownerLbl`, slice C) and passed cleanly on every slice since, so it is cheap and it works. It only runs because I paste it into each one-shot; in CI it would guard every contributor's change.
Author
Owner

Deferred-items sweep complete — v0.6.81, live on both dogfoods. PRs #330, #331, #332, #333.

16 of 17 items closed. One deferred by decision, and it is the only one that needed deferring.

Cluster Items PR
A — rot sweep + guard 4, 5, 8, 12 #330
D — product calls 7, 10 #331
B — content 3, 9, 11 #332
C — backend surface 1, 6, 13, 14, 15, 16 #333

The finding that matters most

Four items recorded as "blocked on a missing endpoint" were already served. I had written them up from intent rather than from the code:

  • Item 14 — auditQuery has parsed from/to as RFC3339 since it was written. Only the inputs were missing.
  • Item 13 — the space lifecycle API landed in #286. The admin inventory just never called it.
  • Item 6 — /admin/info already returned storageDriver and shareCount.
  • Item 15 — RetentionPeriods is a map[string]string. My stated blocker ("rules are free text, the grammar is unverified") was simply wrong; the textarea is a serialisation of structured data.

Net: cluster C needed one endpoint and one boolean, not seven endpoints. Worth remembering next time I record something as blocked — "I have not read this" and "this does not exist" are different claims, and I filed the first as the second.

Two live bugs found by the new guard

internal/webcheck was written first, and everything else in #330 is something it found rather than something I went looking for:

  • Drag-and-drop feedback has been dead since PR1. JS still called classList.add("dragrow"); the CSS still said tr.dragrow. No grab cursor, no drag opacity, no drop highlight, for weeks.
  • The rail inbox dot was polling /api/v1/files every 60 seconds to decide the visibility of an element display:none !important had made unreachable. Found because the removal script refused to proceed on an unrecognised reference — the abort was the discovery.

Deferred, deliberately

Item 2 (per-item notification read state) → #138. It needs per-user persistence, and every runtime state file is exactly what #138 moves into the storage backend. Building it now hands that milestone one more thing to migrate, and picks the schema before the new layout exists.

Audit event count on Overview stays out: no count endpoint exists, and a number inferred from a capped query would look authoritative without being so.

Standing notes

  • The guard cannot see broken combinators (aside > button) — that needs a real DOM and therefore a login. Written into the package doc so nobody assumes more coverage than exists.
  • TestUnusedClassesReport logs and never fails, and this run proves why: it flags teal and gray, which are real and applied via "kvbadge " + badgeCls.
  • t(SET_DESC[id]) is a dynamic lookup the i18n guard cannot check; the splice validates those keys separately.
  • set-quotafield still has no description — I cannot confirm from the frontend whether it caps the instance or sets a per-space default, and a confident wrong description on a quota control is worse than a blank.

For your visual pass

Everything below needs a login, which I cannot do:

  • Retention rules as editable rows, including the path warning (type a deliberately wrong prefix).
  • Space Deactivate → Delete, with the name confirmation.
  • The IdP-managed members notice should actually fire on these dogfoods — the pod logs IdP-managed spaces enabled (power mode), so idpManaged is true and Add/Remove/role should be disabled with the notice showing.
  • Libre Franklin rendering across the five themes.
  • German, French and Italian setting descriptions — Swiss German throughout, no eszett.
**Deferred-items sweep complete — v0.6.81, live on both dogfoods.** PRs #330, #331, #332, #333. 16 of 17 items closed. One deferred by decision, and it is the only one that needed deferring. | Cluster | Items | PR | |---|---|---| | A — rot sweep + guard | 4, 5, 8, 12 | #330 | | D — product calls | 7, 10 | #331 | | B — content | 3, 9, 11 | #332 | | C — backend surface | 1, 6, 13, 14, 15, 16 | #333 | ## The finding that matters most **Four items recorded as "blocked on a missing endpoint" were already served.** I had written them up from intent rather than from the code: - Item 14 — `auditQuery` has parsed `from`/`to` as RFC3339 since it was written. Only the inputs were missing. - Item 13 — the space lifecycle API landed in #286. The admin inventory just never called it. - Item 6 — `/admin/info` already returned `storageDriver` and `shareCount`. - Item 15 — `RetentionPeriods` is a `map[string]string`. My stated blocker ("rules are free text, the grammar is unverified") was simply wrong; the textarea is a serialisation of structured data. Net: cluster C needed **one endpoint and one boolean**, not seven endpoints. Worth remembering next time I record something as blocked — "I have not read this" and "this does not exist" are different claims, and I filed the first as the second. ## Two live bugs found by the new guard `internal/webcheck` was written first, and everything else in #330 is something it found rather than something I went looking for: - **Drag-and-drop feedback has been dead since PR1.** JS still called `classList.add("dragrow")`; the CSS still said `tr.dragrow`. No grab cursor, no drag opacity, no drop highlight, for weeks. - **The rail inbox dot was polling `/api/v1/files` every 60 seconds** to decide the visibility of an element `display:none !important` had made unreachable. Found because the removal script *refused* to proceed on an unrecognised reference — the abort was the discovery. ## Deferred, deliberately **Item 2 (per-item notification read state) → #138.** It needs per-user persistence, and every runtime state file is exactly what #138 moves into the storage backend. Building it now hands that milestone one more thing to migrate, and picks the schema before the new layout exists. **Audit event count on Overview** stays out: no count endpoint exists, and a number inferred from a capped query would look authoritative without being so. ## Standing notes - The guard cannot see broken **combinators** (`aside > button`) — that needs a real DOM and therefore a login. Written into the package doc so nobody assumes more coverage than exists. - `TestUnusedClassesReport` logs and never fails, and this run proves why: it flags `teal` and `gray`, which are real and applied via `"kvbadge " + badgeCls`. - `t(SET_DESC[id])` is a dynamic lookup the i18n guard cannot check; the splice validates those keys separately. - `set-quotafield` still has no description — I cannot confirm from the frontend whether it caps the instance or sets a per-space default, and a confident wrong description on a quota control is worse than a blank. ## For your visual pass Everything below needs a login, which I cannot do: - Retention rules as editable rows, including the path warning (type a deliberately wrong prefix). - Space Deactivate → Delete, with the name confirmation. - **The IdP-managed members notice should actually fire on these dogfoods** — the pod logs `IdP-managed spaces enabled (power mode)`, so `idpManaged` is true and Add/Remove/role should be disabled with the notice showing. - Libre Franklin rendering across the five themes. - German, French and Italian setting descriptions — Swiss German throughout, no eszett.
Author
Owner

Closing as complete/superseded (operator decision, 2026-09-03).

The two unchecked plan boxes (PR2 listing enrichment, PR3 owner/chips) were delivered by v0.6.65 (shared/hold flags + chips), and every field deliberately dropped there for lack of data has since become real and shipped through later work: the owner column (ownermeta sidecars), folder item counts, and lock chips (#315). The redesign itself completed through the admin slices A–D (v0.6.77–v0.6.80) and the 17-item deferred sweep (v0.6.81); the one item deferred by decision — per-item notification read state — lives on under #138/#402, where the watermark model now covers it.

The UI has been iterated on repeatedly since (five-theme system, bell rework, Locks admin page, General settings card), all in daily dogfood use — nothing actionable remains on this issue.

Closing as complete/superseded (operator decision, 2026-09-03). The two unchecked plan boxes (PR2 listing enrichment, PR3 owner/chips) were delivered by v0.6.65 (`shared`/`hold` flags + chips), and every field deliberately dropped there for lack of data has since become real and shipped through later work: the **owner column** (ownermeta sidecars), **folder item counts**, and **lock chips** (#315). The redesign itself completed through the admin slices A–D (v0.6.77–v0.6.80) and the 17-item deferred sweep (v0.6.81); the one item deferred by decision — per-item notification read state — lives on under #138/#402, where the watermark model now covers it. The UI has been iterated on repeatedly since (five-theme system, bell rework, Locks admin page, General settings card), all in daily dogfood use — nothing actionable remains on this issue.
Cordy closed this issue 2026-09-03 01:49:01 +00:00
Sign in to join this conversation.
No labels
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: Cordy/Cairn#311
No description provided.