Skip to content

feat(frontend): migrate to shared @kenn-io/kit-ui components#957

Merged
wesm merged 21 commits into
mainfrom
modest-leavitt-2c631b
Jul 3, 2026
Merged

feat(frontend): migrate to shared @kenn-io/kit-ui components#957
wesm merged 21 commits into
mainfrom
modest-leavitt-2c631b

Conversation

@mariusvniekerk

@mariusvniekerk mariusvniekerk commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Migrates the frontend to the shared @kenn-io/kit-ui component library in the six staged commits from kit-ui's migration guide, each independently reviewable and validated: design tokens, display primitives (Spinner, StatusDot, KbdBadge, EmptyState), stateful primitives (CopyButton, Button, TableHeaderCell, Tooltip), overlays and layout (Modal, Typeahead, FilterDropdown, FindBar, StatusBar, TopBar, DateRangePicker, RefreshControl), utilities and the theme store, and finally enforcement.

Dependency strategy. kit-ui is consumed as a commit-pinned GitHub git dependency (github:kenn-io/kit-ui#215d252 in frontend/package.json). The repo is public, so npm ci clones it anonymously over HTTPS everywhere — no sibling checkout, no registry publish, no CI credentials. Bumping the dependency is a one-line hash change plus npm install. The Docker build needs no special wiring.

What stays local, and why. Thin wrappers inject what kit-ui can't know: localized strings and the app locale (shared/RangePicker.svelte, shared/RefreshControl.svelte), store wiring (SessionFindBar), and domain glue (ProjectTypeahead, the modal family). The i18n-aware formatters, keyboard shortcuts, markdown pipeline, and content/CodeBlock (controlled-copy contract) intentionally remain app-owned; DESIGN.md documents each boundary plus the two remaining upstream gaps (Modal close-X aria-label, TopBar no-active-tab state).

Deliberate behavior changes.

  • Relative date windows now follow kit-ui's semantics: "Last N days" spans N calendar days inclusive of today (previously N+1). Stage 4 had left the picker displaying kit-ui's ranges while queries resolved the old ones; every consumer (preset resolution, rolling window_days URLs, custom-tab seeding, analytics/usage stores) now agrees. Pinned from/to URLs are unaffected.
  • The theme store moved to kit-ui: existing "theme" localStorage values carry over, the legacy high-contrast key migrates at startup, and users without a stored preference gain OS-preference tracking.
  • Responsive breakpoints snapped to the shared 640/760/900 ladder, with JS layout logic (ui.isMobileViewport, SIDEBAR_DESKTOP_BREAKPOINT) derived from the same constants; the insights generated-archive grids collapse on container width because their hard column minimums make viewport gates wrong when the sidebar is open.

Enforcement. npm run check:kit-ui gates CI at zero findings (raw colors tokenized, spacing on the --space-N ladder, shared breakpoints, kit-popover-card chrome). The gate is the plain full-rule kit-ui-check src run — no rules disabled, no paths exempted. Every legitimate exception carries an in-file kit-ui-check-ignore marker with a reason: the definitional token lines in app.css, the trends brown palette slot, and CodeBlock's copy contract.

Where to look. The riskiest changes are the date-window semantics (utils/dates.ts, shared/dateRangeSelector.ts, and the store updates in stores/analytics.svelte.ts/stores/usage.svelte.ts) and the theme-store delegation in stores/ui.svelte.ts. The stage-6 commit is broad but style-only. Visual deltas from token snapping were kept conservative but were not screenshot-verified; the deliberate ones are documented in the stage commit messages.

🤖 Generated with Claude Code

generated by a clanker

@roborev-ci

roborev-ci Bot commented Jul 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (7b36904)

High-risk dependency install issue remains, and several E2E selectors need updating before merge.

High

  • frontend/package-lock.json:451
    • @kenn-io/kit-ui resolves to git+ssh://git@github.com/..., so clean npm ci runs in CI/Docker without GitHub SSH credentials can fail despite docs/comments saying HTTPS anonymous cloning.
    • Fix: use an HTTPS git spec in frontend/package.json and regenerate the lockfile so resolved is HTTPS.

Medium

  • frontend/e2e/termination.spec.ts:34, frontend/e2e/usage.spec.ts:155, frontend/e2e/session-list.spec.ts:72
    • E2E tests still target removed pre-kit-ui classes like .status-dot--unclean and .nav-btn; migrated components now use kit-ui markup/classes, so these selectors are stale.
    • Fix: update selectors to stable roles/labels or the new kit-ui classes, consistent with the updated component tests.

Panel: ci_default_security | Synthesis: codex, 7s | Members: codex_default (codex/default, done, 8m11s), codex_security (codex/security, done, 3m59s) | Total: 12m17s

@roborev-ci

roborev-ci Bot commented Jul 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (f30cc94)

High risk: CI installs can fail because frontend/package-lock.json locks @kenn-io/kit-ui to an SSH GitHub URL.

High

  • frontend/package-lock.json:451
    The new @kenn-io/kit-ui dependency is locked to git+ssh://git@github.com/.... Clean CI/Docker installs run npm ci without SSH credentials, so dependency installation can fail before checks or builds run.
    Fix: Use an HTTPS git spec in frontend/package.json and regenerate the lockfile so resolved uses HTTPS.

Medium

  • frontend/e2e/usage.spec.ts:155; frontend/e2e/session-list.spec.ts:72; frontend/e2e/termination.spec.ts:34
    E2E specs still query old local classes like .nav-btn and .status-dot--unclean, but the migrated UI now renders kit-ui TopBar/StatusDot classes. These locators will not match and will break Playwright CI.
    Fix: Update the specs to use role-based locators or the new kit-ui selectors, e.g. .kit-top-bar__tab and .kit-status-dot--unclean.

Panel: ci_default_security | Synthesis: codex, 9s | Members: codex_default (codex/default, done, 7m47s), codex_security (codex/security, done, 4m0s) | Total: 11m56s

@roborev-ci

roborev-ci Bot commented Jul 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (6f3038a)

High-risk dependency issue found; no security-specific findings.

High

  • frontend/package-lock.json:451
    The lockfile resolves @kenn-io/kit-ui via git+ssh://git@github.com/..., so clean CI/Docker npm ci runs without GitHub SSH credentials will fail even though the package is intended to be fetched anonymously.
    Fix: Use an explicit HTTPS git dependency such as git+https://github.com/kenn-io/kit-ui.git#<sha> in package.json and regenerate package-lock.json.

Medium

  • frontend/e2e/session-list.spec.ts:72
    Several E2E specs still target removed AppHeader/StatusDot selectors (.nav-btn, “More navigation”, .status-dot--unclean) after the TopBar/kit-ui migration, so the E2E CI job will fail once it reaches Playwright.
    Fix: Update the affected specs to use the new kit-ui selectors or accessible roles/labels, including session-list.spec.ts, usage.spec.ts, recent-edits.spec.ts, and termination.spec.ts.

Panel: ci_default_security | Synthesis: codex, 9s | Members: codex_default (codex/default, done, 6m55s), codex_security (codex/security, done, 7m45s) | Total: 14m49s

@roborev-ci

roborev-ci Bot commented Jul 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (bce1886)

Medium issue found: the new language typeahead can be clipped by its settings container.

Medium

  • frontend/src/lib/components/settings/SettingsSection.svelte:32
    SettingsSection still forces overflow: hidden after removing the allowOverflow escape hatch, while LanguageSettings now renders a Typeahead inside it. The dropdown panel can be clipped by the section box, making the language selector hard or impossible to use. Restore an overflow opt-out and pass it from LanguageSettings, or render the typeahead popover outside the clipped section.

Panel: ci_default_security | Synthesis: codex, 6s | Members: codex_default (codex/default, done, 17m24s), codex_security (codex/security, done, 8m12s) | Total: 25m42s

@roborev-ci

roborev-ci Bot commented Jul 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (474cd5c)

Medium confidence: one medium issue remains; no high or critical findings were reported.

Medium

  • frontend/src/lib/stores/trends.svelte.ts:20 — Trends still initializes its default range with daysAgo(365), while the new shared range semantics define “last 365 days” as today plus the previous 364 days. This makes the default Trends query include 366 calendar days and prevents selectionFromRange() from recognizing it as the 1y preset.

    Suggested fix: Initialize the Trends default from rollingRange(365) or a shared default range constant, while preserving window_days=365 if the default is intended to remain rolling.


Panel: ci_default_security | Synthesis: codex, 7s | Members: codex_default (codex/default, done, 17m18s), codex_security (codex/security, done, 4m1s) | Total: 21m26s

@roborev-ci

roborev-ci Bot commented Jul 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (6a13e90)

Medium issue found; no High or Critical findings.

Medium

  • frontend/src/lib/components/trends/TrendsPage.svelte:43
    The default Trends range is documented as rolling, but trendsWindowDays starts as null and bare /trends URLs never write window_days=365. That makes the initial one-year range fixed after the first load, so refreshes after midnight do not advance the window and shared URLs lose rolling intent. Seed trendsWindowDays to 365 for the shipped default rolling range, or persist explicit rolling state alongside the Trends store range and use it when writing/materializing the URL.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 25m53s

@roborev-ci

roborev-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown

roborev: Combined Review (70c7af7)

No issues found.


Reviewers: 2 done | Synthesis: codex | Total: 13m15s

First stage of the staged migration onto the shared @kenn-io/kit-ui
component library (docs/migration.md in the kit-ui repo): consume the
library as a source dependency and let its theme.css own the shared
design tokens that were originally consolidated *from* this app.

app.css keeps only agentsview-specific tokens: the extra agent-identity
accents and their -foreground pairs, tool-category colors, duration UX
states, message-role backgrounds, and the viewport indicator. Two shared
tokens are deliberately overridden: --accent-teal keeps the greener hue
(kit-ui's cyan-leaning teal would collide with --accent-cyan, and both
are used as distinct agent-identity hues) and --header-height stays at
40px until the AppHeader -> TopBar swap in a later stage.

The pinned html font-size is gone in favor of kit-ui's rem type scale
(--font-size-root on body), which resizes on handheld touch devices by
pointer type instead of viewport width. Visual deltas accepted with the
token unification: dark-mode border/green/status-waiting shades shift
slightly, and the font stacks lose the Noto Sans/Fira Code fallbacks.

kit-ui is a private repo consumed via a file:../../kit-ui path dep, so
a sibling checkout is required; CI wiring lands with a later stage.

Baseline for the migration burndown: kit-ui-check --warn reports 281
findings in 92 files.
Second stage of the kit-ui migration (kata rv65/2f79): swap the
low-risk display primitives for the shared library so agentsview stops
carrying private copies of components that kit-ui consolidated from it.

StatusDot moves to kit-ui's status-string API: call sites derive the
status via getSessionStatus and pass a localized label through the new
sessionStatusLabel helper, keeping the i18n contract (shared components
take translated strings as props) while the rendering — including the
waiting speech bubble and pulse animations — comes from the library.
The local component is deleted; TopSessions' test selectors follow the
kit- class prefix.

Hand-rolled spinners in TrendsPage and ActivityInsight become kit-ui
Spinner (wrapped aria-hidden where visible text already announces the
state), and empty placeholders in TrashPage, PinnedPage, MessageList,
and ActivityInsight become EmptyState — ActivityInsight's deliberately
changes from left-aligned to the shared centered layout. SignalPanel's
one-line 'not enough activity' note is not an empty state; it keeps its
inline strip layout under a non-matching class name. The CommandPalette
esc hint becomes KbdBadge; ShortcutsModal's key list keeps raw <kbd>
with a reasoned suppression because it enumerates compound alternatives
and must stay visible on touch devices, where KbdBadge hides itself.

Spinner instances inside AppHeader and RefreshControl are left for the
stage-4 rewrites that replace those components wholesale.

kit-ui-check --warn: 281 -> 265 findings.
Third stage of the kit-ui migration (kata rv65/ask0): CopyButton,
IconButton, and sortable table headers move to the shared library.

All five CopyButton call sites keep controlled mode (parent-owned
copied state through the app's clipboard util) because tests and copy
flows hook that single path; the hover-reveal contract moves to the
library's revealOnHover prop with parents revealing .kit-copy-btn.
PinnedPage's hand-rolled copy button becomes a CopyButton, which needed
a new pinned_copied_message key in all three locales — the shared
component takes pre-translated copied labels as props. The local
CopyButton component and its test are deleted.

The hand-rolled icon buttons in PerfDebugPanel and InsightsPage become
IconButton (required ariaLabel, tokenized hover states). SessionsTable's
sortable headers become TableHeaderCell — aria-sort, arrow indicator,
and numeric right-alignment come from the library; the local table shell
keeps its sticky positioning via a :global rule because the header cells
now render outside this component's style scope.

Deliberately not swapped: CommandPalette's relevance/recency toggle
stays hand-rolled because SegmentedControl cannot suppress mousedown
focus-stealing, and the palette must keep focus in its input; broad
Button adoption is deferred until after stage 4 since most candidate
buttons live in modals and headers that stage replaces wholesale.

kit-ui-check --warn: 265 -> 255 findings.
Fourth and largest stage of the kit-ui migration (kata rv65/e0hc): the
app shell and every overlay now come from the shared library.

AppHeader is rebuilt on TopBar: all eight routes are flat primary-nav
tabs that collapse into a dropdown by measurement, replacing both the
hand-rolled 'More' menu and the 1024/767px width breakpoints; the
command-palette hint degrades field-to-icon via FitStages with
searchMinWidth sequencing the two breakpoints. The 40px --header-height
override is gone — the header standardizes on kit-ui's 44px. Side-region
labels drop while the tabs are collapsed (CSS, so jsdom tests still see
them). One accepted TopBar limitation: on the settings route the first
tab renders as current because TopBar cannot express 'no active tab'.

All seven modals move to Modal (focus trap, scroll lock, Escape and
overlay-close built in) with kit-ui Buttons in their footers, and the
shared .modal-* styles leave app.css; ResyncModal/ImportModal keep their
un-dismissable in-progress guards. OptionTypeahead call sites use kit-ui
Typeahead directly (identical API — it was extracted from this app) and
the local component is deleted; ProjectTypeahead stays as project glue.
The usage FilterDropdown becomes a thin wrapper over kit-ui's, keeping
the CSV include/exclude semantics app-side. SessionFindBar becomes a
store-wiring wrapper over FindBar (pinned variant matches the old
presentation). StatusBar keeps its content but renders through kit-ui's
left/right regions. RangePicker becomes a ~90-line i18n-injection
wrapper (call sites unchanged; the app's resolveRange semantics are
preserved — kit-ui's resolver never feeds the backend); RefreshControl
maps 1:1 and is deleted in favor of the library component.

Known i18n regressions from kit-ui API gaps, to fix upstream: Modal's
close X and RefreshControl's freshness label are hardcoded English, and
weekOfLabel cannot express zh date-first word order. Kept local with
reasoned checker suppressions: the command-palette overlay (top-aligned,
palette-owned focus) and ThreeColumnLayout's pointer-capture sidebar
resizer. jsdom gets a no-op ResizeObserver stub since TopBar, FitStages,
and Typeahead all measure with it.

kit-ui-check --warn: 255 -> 216 findings.
frontend/package.json consumes @kenn-io/kit-ui as file:../../kit-ui — a
sibling source checkout that exists on dev machines but not in a clean
CI, release, desktop, or docker checkout, so every npm ci and frontend
build off a fresh clone was broken (roborev 4565/4566/4572/4576, High).

Publishing to a registry or vendoring would fork the library away from
its consumed-as-source design, so the fix keeps the sibling-checkout
contract and reproduces it in CI: a checkout-kit-ui composite action
clones kenn-io/kit-ui next to the workspace, pins it to the commit
recorded in .github/kit-ui-ref (single place to roll the library
forward), and installs its dev dependencies — required because the app
build loads kit-ui's svelte.config.js. The action runs before every
frontend npm ci: ci.yml (frontend, frontend-node-25, e2e), release.yml
(both binary matrices, incl. the manylinux containers — the reason auth
is an HTTPS token rather than a deploy key, since those images ship git
but not ssh), and the desktop workflows (their sidecar script builds the
frontend). Docker builds cannot see siblings of the build context, so
docker.yml passes the checkout as a named kit-ui build context and the
Dockerfile copies it to /kit-ui, where the lockfile's ../../kit-ui link
resolves.

kit-ui is private: workflows pass secrets.KIT_UI_TOKEN (fine-grained
PAT, contents:read on kenn-io/kit-ui) — an operator must provision that
secret once; the action falls back to an unauthenticated clone and fails
with instructions otherwise, and keeps working unchanged if the repo
ever goes public.

Validated by pointing the local dep at a clean clone of the pinned
commit (c76a816) and running npm ci, svelte-check, the unit suite, and
the production build — all green. The docker path is unverified here
(no local daemon); it follows the documented named-context pattern.
…ken ownership

Follow-ups from the kit-ui migration reviews: an integration test pins the
header copy button's controlled-mode contract (click forwarding into the
app clipboard util and the parent-owned copied aria/title state), which
would otherwise break silently if kit-ui's CopyButton API or class names
change. The app.css comment records that all --accent-*-foreground pairs
are agentsview-owned even for kit-ui's core accents, since kit-ui defines
no foreground tokens and the inks are tuned to the identity badge fills.
The frontend's @kenn-io/kit-ui dependency moves from file:../../kit-ui to
a commit-pinned git dependency (github:kenn-io/kit-ui#<sha>), so the pin
lives in package.json/package-lock.json rather than a side-channel
.github/kit-ui-ref file, and npm ci resolves it anywhere git credentials
for the private repo exist. kit-ui ships only src/lib, bin, and the check
rules in its pack (no svelte.config.js), which also removes the need to
npm-install kit-ui's devDependencies before building the app; kit-ui-check
now runs via npx from node_modules.

The checkout-kit-ui composite action (sibling clone + install) is replaced
by kit-ui-auth, which only rewrites the kenn-io/kit-ui ssh URL recorded in
the lockfile to KIT_UI_TOKEN-authenticated HTTPS. Docker builds drop the
named kit-ui build context; npm ci inside the frontend-build stage
authenticates the same way via a kit_ui_token BuildKit secret so the token
never lands in an image layer.

Validation: npm ci from the updated lockfile, svelte-check (0 errors),
full unit suite (1724 passing), production build, and kit-ui-check (216,
unchanged) all pass locally; actionlint is clean. The Docker image build
was not exercised because no local Docker daemon was available.
The kit-ui GitHub dependency was wired up assuming a private repository:
a kit-ui-auth composite action rewrote its ssh URL to PAT-authenticated
HTTPS in every workflow, and the Docker build threaded a BuildKit secret
into npm ci. kenn-io/kit-ui is public, so none of that is needed — npm
clones GitHub-hosted git dependencies anonymously over HTTPS even though
the lockfile records a git+ssh URL for them.

Verified by running npm ci from the unchanged lockfile with ssh disabled
(GIT_SSH_COMMAND=/usr/bin/false), an empty git config, and a cold npm
cache; the install succeeds. The KIT_UI_TOKEN secret no longer needs to
be provisioned. The Docker image build itself was not exercised (no local
daemon); actionlint reports only pre-existing shellcheck notes.
…sive semantics

Stage 4 left the app with two meanings of "Last N days": kit-ui's
RangePicker labels presets and seeds its Custom tab with N calendar days
inclusive of today (from = today-(N-1)), while the app's presetRange and
rollingRange resolved the same selection to an N+1-day span (from =
today-N). Switching from a relative preset to Custom therefore seeded a
start date one day later than the range actually being queried.

Adopt kit-ui's definition in both helpers so every consumer — preset
resolution, rolling window_days URLs, custom-tab seeding, and preset
detection — agrees. This is a deliberate behavior change: relative
windows now span N days instead of N+1, so "7d" queries one fewer
trailing day than before. Existing pinned from/to URLs are unaffected;
rolling window_days links rematerialize under the new math.

Also from the stage-4 reviews: the dark high-contrast e2e check now
measures a real kit-ui solid Button (Import modal confirm) instead of a
synthetic element styled with token pairs kit-ui's Button does not
actually use, and DESIGN.md documents the range-semantics contract plus
the known kit-ui gaps (Modal close-X aria-label, RefreshControl age
label, weekOfLabel word order, TopBar no-active-tab) with their
resolution path.
Utility implementations duplicated in kit-ui become re-exports so one
copy owns the behavior: copyToClipboard, debounce, truncate, formatCost,
formatNumber, and formatTokenCount now come from kit-ui's util subpaths,
with call sites and tests still importing the app module paths so vitest
module mocks keep working. projectColor delegates to kit-ui's hashColor
but deliberately keeps the app palette — kit-ui's default palette differs
in order and content, and switching would reshuffle every project's
established identity color. The i18n-aware formatters
(formatRelativeTime, formatTimestamp, formatRefreshAge) stay local on
purpose; kit-ui's equivalents are English-only. PublishModal and
RemoteSettings drop their hand-rolled navigator.clipboard wrappers for
the shared util.

Theme state moves to kit-ui's theme store: initTheme reuses the app's
historical "theme" storage key (its light/dark values are valid kit-ui
modes, so existing preferences carry over) and the legacy
agentsview-high-contrast flag migrates to the derived
theme-high-contrast key at startup. UIStore.theme/highContrast become
getter/setter delegates, kit-ui now owns the root dark/high-contrast
classes and persistence, and users without a stored preference gain OS
preference tracking via kit-ui's system mode. The desktop postMessage
theme:set control keeps working through the setter.

Keyboard shortcuts and the markdown pipeline intentionally stay local:
the app's shortcut handling is context-dependent imperative code, and
the markdown pipeline carries bash wrapper tags, asset URL rewriting,
and XML escaping that kit-ui's renderer does not model.
…ange

Follow-up to the N-days-inclusive alignment (81ec298): AnalyticsStore
and UsageStore still derived their rolling windows with the raw
daysAgo(windowDays) N+1 form instead of rollingRange(), so their queries
disagreed by one day with the picker, preset detection, and every other
rolling consumer. Flagged by roborev jobs 4615/4616 on the alignment
commit.
Burns the kit-ui-check backlog down to zero across every component
(raw colors mapped to theme/app tokens, off-ladder gaps snapped to the
--space-N scale, @media widths snapped to the shared 640/760/900
breakpoints) and turns the checker into a CI gate: the frontend job now
runs check:kit-ui without --warn. app.css is exempt from the raw-color
rule because it defines the app's token layer, so its color values are
definitional; the gate runs the remaining rules over all of src and
raw-color over src/lib plus App.svelte.

The few deliberate exceptions carry kit-ui-check-ignore comments with
reasons: the tuned brown slot of the trends series palette (nearest
token would collide with the amber slot), and content/CodeBlock's
markup, which stays local because kit-ui's CodeBlock hardcodes
self-managed copy and offers no hook for the app's controlled clipboard
contract or the in-session find highlighting.

The store's isMobileViewport matchMedia moves from 768px to kit-ui's
MEDIA.medium (760px) so JS and the newly snapped CSS agree on where the
mobile layout starts — previously a 761-767px band reported mobile in
JS while CSS rendered desktop.
…rols

The pin moves to kit-ui main at 47ecfd3, which lands the i18n hooks this
migration was waiting on (RefreshControl formatAge, DateRangePicker
weekOfLabel {date} substitution, locale props) plus the RangePicker →
DateRangePicker rename and the popover/tone unification work beneath it.

The shared RangePicker wrapper now passes the week-of template through
with its {date} slot intact, so date-first locales keep their word
order, and a new shared RefreshControl wrapper injects the app's
localized age formatter — closing the two known mixed-language
regressions from stage 4. Both wrappers also pass locale={getLocale()}
(newly re-exported from the i18n facade) so calendar dates and the
refresh tooltip follow the app language setting rather than the browser
locale.

The upgrade also brings a new hand-rolled-popover-card checker rule; the
five header/filter/perf dropdowns adopt the shared kit-popover-card
chrome class instead of restating it in scoped CSS. Tests tracking
kit-ui internals move with upstream: the DateRangePicker mode switch is
now a stock SegmentedControl (radio roles, not tabs) and the trigger
class gained the date- prefix.
…s on content width

Two seams left by the breakpoint snap in the stage-6 conformance pass
(roborev 4635):

SIDEBAR_DESKTOP_BREAKPOINT stayed at 768 while the CSS and
ui.isMobileViewport moved to kit-ui's 760px medium breakpoint, so at
761-767px the sidebar rendered desktop CSS with non-desktop resize
logic. It now derives from BREAKPOINTS.medium + 1 so every layout
decision shares one source of truth.

The insights generated-archive grids have hard minimum column widths
(controls: 180+130+240px; layout: a 240px list rail), and moving their
collapse from 980px to the shared 900px viewport gate reopened a
horizontal-overflow window at 901-980px with the sidebar open. They now
collapse via a container query on the section's actual inline size, so
available content width — not the viewport — decides when they stack.

Also bumps the kit-ui pin to 215d252, which hardens the i18n hooks
adopted in the previous commit (RefreshControl clamps its clock so
custom formatAge formatters never see a negative age; capped Intl
caches) with no API changes.
…ules

The @container collapse for the generated-archive grids was declared
before the base grid-template-columns rules; container queries add no
specificity, so source order let the base declarations win and the
grids never stacked (roborev 4641). The block now follows both base
rules, and the comment records the ordering constraint.
The CI gate previously split the checker into two scoped invocations,
path-exempting app.css from the raw-color rule. kit-ui's stage-6
acceptance is the plain full-rule run being green with every exception
carrying an in-file kit-ui-check-ignore reason, so the gate is now a
single kit-ui-check src over everything and app.css's definitional
token lines carry explicit per-line markers instead of a silent path
carve-out. One comment mentioning a hex value in prose was reworded
rather than suppressed.
…+https

The Playwright specs still drove the pre-migration header and status
bar: .nav-btn tabs, the removed More-navigation menu (Recent Edits is a
top-level TopBar tab now), .status-dot--unclean, and .status-left. They
now use the kit-ui selectors. The suite also runs at a 1600px viewport:
at Playwright's 1280px default the eight TopBar tabs collapse into the
nav SelectDropdown by measurement, so the expanded tab row the
navigation specs exercise never rendered. Full chromium suite passes
70/70 (1 skipped).

The kit-ui dependency is now declared as an explicit
git+https://github.com/kenn-io/kit-ui.git#<sha> spec. npm's lockfile
still records the resolved URL in its canonical git+ssh form — npm
rewrites it on every install, so that string cannot be pinned to https —
but the fetch is anonymous HTTPS regardless (verified with SSH disabled
and a cold npm cache); AGENTS.md documents this so it doesn't get
re-flagged as an auth problem.
The WebKit CI job failed on two specs. The nav specs assumed the
TopBar's expanded tab row, but the collapse decision is by measurement
and engine-dependent: WebKit's first layout pass measures the side
regions wider than Chromium's, and TopBar freezes that bloated
requirement in its expand hysteresis, so at the same 1600px viewport
WebKit renders the collapsed SelectDropdown where Chromium renders
tabs (upstream kit-ui issue — the frozen expandUsed never re-derives
after the transient settles). A shared helper now drives whichever nav
mode rendered, and the usage active-tab assertion checks the dropdown
value when collapsed.

Runtime stability also allowlists WebKit's benign "ResizeObserver loop
completed with undelivered notifications" console error, which the
TopBar measurement triggers by re-laying-out inside its own observer
callback.

Both Playwright projects pass locally: chromium and webkit each 70/70
(1 skipped).
…antics

TrendsStore initialized its default window with daysAgo(365) + today(),
which spans 366 calendar days under the shared N-days-inclusive
"last N days" definition. That extra day made TrendsPage's
selectionFromRange() treat the default as a custom range instead of the
1y preset. Seed from rollingRange(365) so the default is exactly 365
days inclusive and reads as the relative preset; the window stays
rolling (window_days=365 unchanged).
…ahead

The origin/main merge added UsagePairwiseComparisonPanel, which imports
OptionTypeahead — the local component this branch deleted during the
kit-ui migration — so the merged tree failed svelte-check (dangling
import) and the kit-ui-check gate (hand-rolled empty-state, off-ladder
gap, nonstandard 800px breakpoint).

Swap OptionTypeahead for kit-ui Typeahead (drop-in prop-compatible), set
the width custom properties on the wrapper so they inherit into
.kit-typeahead, rename the inline status notices off the empty-state
class (they are one-line panel notes styled with .error-bar, not the
centered EmptyState component), and snap the gap and breakpoint to the
shared scale — matching how stage 6 handled the rest of usage/.
Bare Trends URLs are supposed to carry rolling one-year intent, but the page initialized its rolling window state as absent and cleared it whenever the URL lacked date params. That made the first materialized URL look fixed, so reloads and shared links could stop advancing after midnight.

Keep the shipped default as a 365-day rolling window unless the URL or yoke state explicitly selects a fixed range, and materialize that rolling window before the initial fetch so the first request and URL agree.
@mariusvniekerk mariusvniekerk force-pushed the modest-leavitt-2c631b branch from 61d3570 to 0b3d3c4 Compare July 3, 2026 17:14
@roborev-ci

roborev-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown

roborev: Combined Review (0b3d3c4)

No issues found.


Reviewers: 2 done | Synthesis: codex | Total: 16m39s

@mariusvniekerk mariusvniekerk marked this pull request as ready for review July 3, 2026 18:17
@wesm wesm merged commit dfc6a66 into main Jul 3, 2026
23 checks passed
@wesm wesm deleted the modest-leavitt-2c631b branch July 3, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants