Commit Graph

25 Commits

Author SHA1 Message Date
Ayaan Zaidi c85be96d6e fix(ui): bootstrap source-only locales (#131015)
Allow source-only locale contributions to load intentional English bootstrap copy until post-merge generated memory exists. Derive refresh targets from canonical locale data while preserving strict validation.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-27 22:43:35 +05:30
Peter Steinberger 0dbd5c81d5 feat(plugins): one consent screen for plugin capabilities, bound to the reviewed artifact (#130168)
* feat(plugins): surface plugin capability consent in Control UI and CLI

Adds plugins.inspect (declared manifest surface, operator grants, install
provenance/integrity, ClawHub trust), a Control UI consent dialog on install
and external-plugin enable, a server-side acceptance gate persisted on the
install record, artifact-anchored widen diffing, and --accept-capabilities
for non-interactive CLI use.

NOT READY TO LAND: autoreview found critical gaps (see PR notes) — the
declared surface omits 20 of 21 contract families, native plugins always
report zero hooks, several install/enable paths bypass the gate, and the
acknowledgment is not bound to the reviewed surface.

* refactor(plugins): bind capability consent to the reviewed surface

Collapses the consent error payload to the fields the client cannot fetch
(reviewToken, widened, acceptedAt) and pulls identity/declared/grants/source/
trust from plugins.inspect, shrinking the registry-free protocol reader from
395 to 91 lines and removing its divergence from the closed schema.

Acknowledgment now carries the SHA-256 reviewToken of the surface the operator
saw; the server recomputes the final staged artifact's surface and rejects any
mismatch before persisting acceptance. That closes review-then-swap, laundering
of forged acceptance through an unchanged update, and cross-artifact replay.

All 22 manifest contract families are now declared, hashed and diffed, so a
privileged family such as gatewayMethodDispatch can no longer be added without
re-consent. Consent reads the manifest runtime discovery will execute, ambiguous
install ownership fails closed, integrity resolution has one owner and no longer
labels npm SHA-1 shasums as SHA-256, and code plugins disclose that hooks
register at runtime instead of rendering an empty "no hooks" row.

* fix(gateway): register plugins.inspect in method inventories and regenerate protocol

Adds plugins.inspect to the advertised-method inventories (widening the
fixed-size slice windows so older indices stay stable), regenerates the Kotlin
protocol bindings, drops an unused exported type, and replaces two nested
conditional spreads with a plain conditional.

* refactor(plugins): split oversized consent modules and clear lint findings

Extracts the MCP controller out of the plugins page, unchanged-install
reconciliation out of update-installed, and the install lifecycle suite out of
the management-service tests, bringing all three back under the max-lines limit
without suppressions. Also renames a shadowed binding, drops an unnecessary
generic, removes a spread-to-modify in a map, and types catch callbacks as
unknown.

* chore(protocol): regenerate Kotlin bindings after rebase

* feat(plugins): let chat /plugins install review and accept capabilities

The consent gate applies to chat installs too, but the command had no way to
give consent, so external installs dead-ended on a CLI-only flag. Chat now
replies with the plugin's declared capability surface and the exact command to
rerun, and accepts a trailing --accept-capabilities mirroring the existing
--force acknowledgement. ClawHub trust acknowledgement stays CLI-only.

Staged-artifact verification is unchanged: the reviewToken is still checked
against the final artifact before acceptance is recorded.

* refactor(plugins): single-source the declared-surface groups and manifest precedence

The ordered capability group list was defined independently in the consent
engine, the protocol error reader, the CLI formatter and the Control UI, so a
new contract family had to be added in four places with nothing enforcing it.
All four now derive from one canonical list in the protocol schema with a
compile-time exhaustiveness guard.

Native-versus-bundle manifest precedence is centralized in one helper that both
discovery and staged consent call, so the two cannot drift again — that
divergence was a real bug where consent read one manifest and the runtime
executed another.

Also documents that carrying acceptance forward requires pinned artifact
integrity, so integrity-less sources such as local paths ask on every install.

* fix(plugins): enforce reviewed consent across activation flows

Route setup, repair, linked installs, updates, and chat activation through artifact-bound capability consent. Reuse canonical package discovery and recheck staged activation before config publication. Invalidate stale Control UI review requests on reconnect.

Verified focused owner and sibling tests, runtime rebuild, and real isolated CLI/Gateway install, inspect, enable, widening, and stale-token rejection flows.

* test(plugins): cover beta installs through capability consent

* test(plugins): align consent fixtures with staged artifacts

* fix(ui): review staged plugin capabilities once

* test(ui): inline the remaining plugin consent confirmation

* test(plugins): verify consent with deferred install transactions

* refactor(setup): share inference execution plan construction

* test(ui): settle applied config before deferring refresh

* fix(plugins): protect consent provenance and reuse acceptance
2026-08-27 02:58:07 -07:00
Peter Steinberger bbd66c475e fix(plugins): fail closed when plugin runtime is unavailable during registration (#130597)
* fix(plugins): fail closed when plugin runtime is unavailable

Core fabricated an empty object as `PluginRuntime` for the `cli-metadata`
and `setup-only` registration passes, so any plugin touching `api.runtime`
during `register()` died with an opaque
`TypeError: Cannot read properties of undefined (reading 'openSyncKeyedStore')`
that named neither the contract nor the mode.

Replace those fabrications with one shared fail-closed runtime that throws a
named, actionable error identifying the plugin and registration mode. Symbol
reads stay inert so inspection and reflection cannot trigger the guard.

Also resolve the lightweight `cli-metadata` entry beside the resolved plugin
entry, not only at the package root. Published plugin packages emit it at
`dist/cli-metadata.js`, so the probe always missed and the loader fell back to
executing the full heavy entry for CLI metadata collection.

Documents which registration modes expose a live `api.runtime`; the SDK
reference previously claimed it was injected into every plugin.

* fix(ui): restore sidebar lazy import boundaries

Remove the redundant viewer-facepile dynamic import: sidebar consumers
already register that element through the static graph.

Import pet data and sprite helpers from their owning leaf modules, delete
the registration module's re-export barrel, and make the element itself
the lobster-pet.runtime.ts lazy boundary. Keep shared sprite styles with
the look renderer so standalone pages do not need element registration.

Regenerate the measured boot inventory with the canonical browser probe.
Use Lit's typed lifecycle map to remove the obsolete assertion baseline
entry and keep the now-private facepile variant type local.

The full build has no INEFFECTIVE_DYNAMIC_IMPORT warnings. Relevant UI
coverage passes (503 tests plus 5 Chromium E2E tests); the unchanged plugin
runtime regression suites pass all 58 tests. The broad plugin suite still
reports schema-version, doctor-closure, hook-process, and catalog failures.
2026-08-26 19:45:39 -07:00
Vyctor H. Brzezowski 1c1673cc35 fix(ui): structure chat transcript attachments by kind (#127076)
* fix(ui): structure chat transcript attachments by kind

* Refine attachment previews

* Refine attachment cards and previews

* Refine audio attachment controls

* Refine attachment cards and previews

* Move audio timer before progress

* Add attachment link fixture

* Make attachment downloads secondary

* Add attachment file icon system

* Tune attachment icon sizing

* Compact attachment preview headers

* fix(ui): align large attachment card inset

* feat(ui): color attachment glyphs by family

* feat(ui): use icon-only attachment actions

* fix(ui): space compact attachment metadata

* fix(ui): finalize attachment card contracts

* Revert "fix(ui): finalize attachment card contracts"

This reverts commit 41b28970b5.

* fix(ui): reduce mobile waveform density

* fix(ui): restore native image rendering

* fix(ui): align and enlarge chat images

* fix(ui): restore open labels on file cards

* fix(ui): simplify labeled attachment actions

* fix(ui): size waveforms from container width

* fix(ui): update attachment open icon

* fix(ui): use continuous corners for chat images

* fix(ui): preserve sidebar video playback

* fix(ui): retain attachment kind in sidebar

* fix(ui): refresh open sidebar attachments

* fix(ui): bound delimited attachment previews

* fix(ui): harden attachment preview lifecycle

* fix(ui): reset lazy attachment previews

* style(ui): format attachment preview code

* fix(ui): align attachment sidebar content

* fix(ui): constrain CSV attachment previews

* fix(ui): keep text attachments compact

* fix(ui): block external document previews

* fix(ui): verify document iframe loads

* fix(ui): render honest audio waveforms

* test(ui): align attachment player expectations

* test(ui): exercise same-origin CSV previews

* test(ui): use browser-origin CSV fixtures

* style(ui): format attachment fixes

* fix(ui): normalize document preview URLs

* test(ui): clarify CSV fixture indices

* fix(ui): stabilize audio time label

* fix(ui): preserve external attachment downloads

* fix(ui): avoid eager video attachment loading

* fix(ui): parse TSV previews by attachment type

* fix(ui): retry managed attachment resolution

* style(ui): format attachment review fixes

* fix(ui): pass attachment type to table preview

* test(ui): normalize compact card label

* fix(ui): preserve sidebar attachment downloads

* test(ui): align attachment card expectations

* style(ui): order attachment imports

* fix(ui): isolate document attachment previews

* fix(ui): release Files attachment ownership

* fix(ui): resolve sidebar attachments from live context

* fix(ui): preserve attachment video aspect ratios

* style(ui): format attachment surfaces

* fix(ui): apply renewed video tickets on interaction

* fix(ui): restore visible attachment waveforms

* fix(ui): fade truncated CSV previews

* fix(ui): lazy load attachment previews in view

* fix(ui): preview image attachments inline

* test(ui): remove link attachment fixture

* fix(ui): block network in HTML attachment previews

* fix(ui): keep image hover actions stable

* feat(ui): preview SVG attachments safely

* perf(ui): split attachment preview styles

* fix(ui): harden attachment preview ownership

* refactor(ui): deslop attachment rendering

* refactor(ui): render transcript attachments as cards

* fix(ui): keep remote SVG attachments CSP-safe

* test(ui): keep chat pane suite within lint limit

* test(ui): cover Files shortcut cleanup

* fix(ui): normalize structured SVG metadata

* fix(ui): classify SVG attachments by source

* fix(ui): preserve attachment type precedence

* perf(ui): keep attachment CSS out of startup
2026-08-26 09:59:11 -03:00
Peter Steinberger 81fb7e69c4 improve(ui): fold the sidebar update action into the Inbox (#129977)
* improve(ui): fold the sidebar update action into the Inbox

The footer update circle duplicated the Inbox's update entry: same
attention state, same dismissal, one extra permanently-tinted control
plus layout shims reserving space for it. The update now surfaces only
as an Inbox entry — badge for attention, full action row (update, hold,
review, dismiss-until-restart) in the panel.

Deletes the footer button, its hover-dismiss, sidebar-footer-update.css,
and the 80px/88px width compensations; moves the floating-cluster rules
to sidebar-attention-floating.css and the build-chip text rules next to
their base class in layout.css. Removes the dead startSidebarUpdateAttention
shortcut and two orphaned i18n keys.

* fix(ui): integrate #129920 top-left alignment with the Inbox-only footer

Ports the web-chrome floating-cluster geometry to sidebar-attention-floating.css
and points the titlebar alignment test at the always-present Inbox button; the
update no longer changes footer geometry, so its scenario plumbing goes.
2026-08-26 01:12:29 -07:00
Vyctor H. Brzezowski c71c16ce1a fix(ui): preserve routable models across reconnect (#128694)
* fix(ui): refresh model catalog after reconnect

* chore(ui): refresh boot manifest

* chore: refresh PR merge ref

* chore(ui): refresh boot manifest after rebase

* chore(ui): align boot manifest with current main
2026-08-25 17:43:26 -03:00
Peter Steinberger 053b89d80f improve(ui): open short session links without extra lookup (#128778)
* perf(ui): remove short session route waterfall

* docs: clarify short link gateway requirement
2026-08-24 09:33:20 -07:00
Peter Steinberger 8fe2c1b83c perf(ui): consolidate Control UI boot chunk graph for HTTP/1.1 gateways (#128514)
* perf(ui): consolidate Control UI boot chunk graph for HTTP/1.1 gateways

The Control UI boot flow (app shell + sidebar + chat route) lazily loaded
~124 automatic chunks in one burst after the gateway handshake, which the
gateway's HTTP/1.1 transport serializes into ~24 six-connection round-trips
on high-latency links (Tailscale, remote gateways).

Add a measured boot-module manifest (ui/config/control-ui-boot-modules.json,
regenerated via pnpm ui:boot-manifest:gen) and a control-ui-boot codeSplitting
group that merges exactly that module set into a handful of chunks with
recursive dependency inclusion. Lazy islands (locales, ghostty-web, novnc,
non-default routes) keep their own chunks; stale manifest entries degrade
gracefully back to automatic chunking.

Measured on the built dist with the mocked gateway (chat route, 3 runs):
unique boot JS requests 140 -> 45, raw boot JS 3751 -> 3717 KiB, chat
composer interactive at simulated 50 ms RTT ~1600 ms -> ~575 ms.

Largest-CSS budget rises 45 -> 47 KiB for the merged boot CSS; startup JS
gzip baseline ratchets down (345049 -> 339214 B) as consolidation shrinks
the startup graph.

* chore(ui): refresh boot module manifest after rebase onto current main

* fix(ui): stop the pending lazy shell action replay loop starving boot

When a pending lazy shell action (command palette open, panel toggle)
replayed while the shell was still splash-gated, the dispatched event had
no rendered element to consume it and re-entered requestLazyElement in a
microtask cycle: request -> load -> replay -> dispatch -> request. The
cycle starved tasks (Gateway WebSocket messages included), so the boot
never finished and the recovery e2e froze on the splash screen.

Gate replay on the element actually being rendered: the controller skips
the action after load until the host's render root contains the tag, and
restorePendingLazyAction skips dispatch while a defined element is still
render-gated. The host retries after every completed update, so the replay
fires on the update that first renders the element. Regression test fails
on the pre-fix controller.

* fix(ui): re-anchor the scope-upgrade details popover before opening

wa-popover resolves its `for` target once per property change and never
re-resolves a missing or replaced anchor. The trigger with the shared id
can render after the popover's first update (the header trigger ships with
the lazy chat chunk), leaving the opened popover permanently invisible:
active popup with a native [popover] part stuck at UA display:none because
showPopover() never ran without an anchor. Re-arm the watcher when opening
while the anchor is missing or disconnected.

* test(ui): compare settled layouts in device-scope stability assertions

The 0.5px no-move assertions sampled geometry that later reflowed when the
details surface's first render fetched glyph subsets, reporting sub-pixel
drift the open never caused. Burn in the one-time open per context and
sample the baseline adjacent to the click.

* fix(ui): map the keyboard shortcuts dialog in lazy replay gating

Current main added the keyboard-shortcuts lazy shell event; the replay
gate's exhaustive event-to-element record needs its entry.

* chore(ui): refresh startup budget baseline after rebase onto current main
2026-08-24 03:55:37 -07:00
Peter Steinberger e879376f18 feat(control-ui): Cmd+/ keyboard shortcuts overview from a declarative catalog (#128563)
* feat(control-ui): add Cmd+/ keyboard shortcuts overview dialog

A declarative shortcut catalog (keyboard-shortcut-contract/catalog) now owns
every Control UI chord: matching, platform-aware formatting, and the new
lazy-loaded Cmd+/ (Ctrl+/ elsewhere) overview dialog all derive from it, so
the overlay cannot drift from real handler behavior. Migrates the scattered
hardcoded shortcut labels (palette, debug overlay, appearance, sidebar,
terminal, workspace) and the duplicated Apple-platform check onto the catalog,
which also makes the previously meta-only sidebar and workspace chords
reachable on non-Apple platforms. The Slash matcher keeps Latin layouts that
put another printable on the physical Slash key (German Cmd+'-' zoom) intact.

* feat(control-ui): list non-obvious shortcuts in the Cmd+/ overview

Adds the invisible-but-real shortcuts to the catalog: composer history
recall (up/down), Esc dictation cancel, new-session-page submit (follows
the same chatSendShortcut preference as chat send), image-viewer zoom
(+/-/0), and the sidebar multi-select mouse chords (mod+Click,
Shift+Click) via a display-only Click key token. Multi-combo rows now
group their chips on the right instead of spreading across the row.

* fix(control-ui): keep Ctrl+Shift+Backquote toggling the terminal

The catalog matcher's exact-Shift comparison rejected the shipped
Ctrl+Shift+` terminal chord that main's predicate accepted (Shift was
deliberately unconstrained for layouts where the Backquote key is
shifted). Backquote now joins '/' as a Shift-insensitive key, with a
regression test. Found by ClawSweeper review on #128563.
2026-08-24 00:26:56 -07:00
Peter Steinberger bbbd70542b feat(sessions): recover offline device placements (#126284)
* feat(sessions): recover offline device placements

* chore(protocol): refresh session placement models

* perf(ui): lazy-load session placement recovery

* test(ui): remove dropdown timing assertion

* test(ui): await committed cloud recovery route

* fix(sessions): complete explicit abandonment locally

* fix(sessions): fence lists by runner availability

* fix(ui): preserve canonical session freshness

* fix(sessions): preserve recovery contracts after rebase

* test(ui): await durable cloud recovery entry

* test(gateway): complete current runner fixtures

* fix(gateway): publish runner availability edges

* fix(ui): preserve shared session freshness

* fix(ui): preserve canonical sidebar session state

* fix(sessions): preserve abandoned partials and run-owned replies

* fix(sessions): resume durable abandonment retries

* test(gateway): compose provisioning replay with runner availability

* fix(sessions): publish recovered move transitions
2026-08-20 09:59:59 -07:00
Peter Steinberger 5eda48ed99 fix(ui): recover failed lazy surfaces (#126409) 2026-08-19 12:27:35 -07:00
Peter Steinberger 75bcc5cebe fix(ui): keep Control UI device identity working on plain-HTTP origins (#124724)
* fix(ui): keep Control UI device identity working on plain-HTTP origins

@noble/ed25519 defaults its SHA-512 provider to crypto.subtle, which
browsers gate to secure contexts, so device identity silently vanished
on http:// LAN dashboards and connects fell back to shared-credential
auth with no pairing. Wire a lazy pure-JS @noble/hashes fallback for
SHA-512 and the fingerprint SHA-256, and drop the isSecureContext gate
in the connect path. Secure contexts keep the platform digests and pay
no startup bytes: the fallback loads as its own lazy chunk, kept out of
the gateway-runtime startup chunk on purpose.

* test(ui): cover device identity minting and signing without crypto.subtle

New jsdom regression suite fails on pre-fix code (subtle-less crypto stub
with getRandomValues, which real insecure contexts keep). Rewrites the
gateway connect tests that previously asserted the device-less insecure
fallback: an insecure context now attaches a device identity.

* docs(web): plain-HTTP dashboards now pair with a device identity

The signing key never crosses the wire, so HTTP+pairing is strictly
stronger than the old HTTP token-only fallback; HTTPS (Tailscale Serve)
stays the recommendation for transport privacy.

* fix(ui): drop unnecessary boolean literal compare in secure-context timing meta

* test(ui): declare device.id on the connect-frame test shape

* test(ui): split the subtle-less scope-upgrade e2e into the two real invariants

Without crypto.subtle the browser can now sign, so the banner offers the
explicit admin upgrade; manual-only guidance is reserved for browsers that
cannot mint an identity at all (no WebCrypto RNG). Also corrects the
connect-path comment: blocked storage yields an ephemeral identity, only a
failed mint degrades device-less.

* fix(ui): address review findings on the HTTP device-identity path

- Storage-blocked pages keep one stable in-memory identity per page
  lifetime instead of minting a fresh unpaired key on every reconnect,
  and a write-rejecting store no longer fails the mint (regression tests
  bite pre-fix).
- Connect timing now reports the real browser secure-context fact via a
  shared browserSecureContext() helper instead of inferring it from
  device-identity presence.
- Docs state the accepted trusted-proxy contract: browsers attach a
  device identity on every origin, so first connects follow the standard
  pairing flow (deviceAutoApprove or a one-time approval); device-less
  admission remains only for browsers that cannot mint an identity.

* refactor(ui): trim the connect-path additions under the max-lines cap
2026-08-16 12:20:00 -07:00
Peter Steinberger bfe1f33ea0 improve(ui): make Control UI feel native on mobile (#122492)
* improve(ui): make Control UI feel native on mobile

* fix(ui): keep coarse-pointer input floor text-scale aware

* fix(ui): let self-sized controls opt out of the touch input floor

* fix(ui): fold per-control coarse-pointer font floors into the shared touch floor
2026-08-12 00:46:35 -07:00
joshavant 5f7ea6b548 fix(ui): lazy-load run inspector copy 2026-08-11 18:44:38 -05:00
Peter Steinberger cad77fb39c refactor: consolidate remaining coercion helpers (#122020) 2026-08-11 10:22:01 -07:00
Peter Steinberger 7e78de747b refactor(ui): replace duplicated locale bundles with canonical translation memory (#117157)
* refactor(ui): generate locale bundles from canonical translation memory

* fix(ui): keep server-only locale config out of browser test helpers
2026-07-31 19:40:27 -07:00
Peter Steinberger 85fda04df7 feat(ui): surface queued-outbox counts in composer hint, session rows, and offline footer (#112649)
* feat(ui): surface queued-outbox counts in composer hint, session rows, and offline footer

Follow-up to the offline-state unification: queued sends were mechanically
safe (durable outbox with reconnect replay) but only visible inside the
open session's thread.

- Composer offline hint now includes the visible session's queued count.
- Session rows show a clock badge with the per-scope outbox count,
  independent of connection state (covers waiting-idle and failed too),
  with alias-safe scope resolution so agent-main never double-counts.
- The offline footer button appends the aggregate ("· N queued"); the
  connected state stays completely silent.
- One narrow subscription seam (subscribeStoredChatOutboxChanges) added
  in composer-persistence; no send/drain logic touched.

* perf(ui): keep the startup bundle under budget with a lean outbox read module

The queued-count feature statically imported composer-persistence from
startup modules, hoisting the chat page's persistence machinery into the
startup chunk and breaking the Control UI startup JS gzip budget
(319.6 KiB > 314.0 KiB limit).

Split ownership instead of gaming the budget: a lean read/subscribe
module (ui/src/lib/chat/outbox-store.ts + codec/draft-state) serves
startup consumers (app-host, sidebar), while writes, migrations, and
drain stay in the lazy chat chunk (composer-outbox-store/composer-storage);
composer-persistence keeps its export surface for chat callers. Startup
is back to 313.8 KiB gzip at 12 requests with no chunking-config changes.

Also fixes an autoreview finding in the new summary: legacy bare-main
outbox rows now resolve through session defaults (online) or the
persisted mainAlias (offline reload) instead of trusting the row's stale
embedded agent id, so badge counts key to the same scope the sidebar
resolves. The shared footer status renderer is deduplicated into
session-row-badges.

* fix(ui): correct type-only import and const tuple in outbox split

* perf(ui): idle-load the outbox summary so startup carries no outbox code

The lean outbox read module still cost ~2.8 KiB of startup gzip against
1.5 KiB of budget headroom. Follow the sidebar chrome pattern
(lobster-pet/facepile): app-host idle-loads outbox-store, subscribes on
arrival, and passes the sidebar a resolver callback instead of letting
startup modules import scope resolution. Badges and counts hydrate
moments after load; before that the summary is empty by design.

Failed chunk loads recover on browser online events and, because chunks
are usually served by the gateway itself, on gateway reconnect — the
exact moment the offline badges become relevant again.

Raise the initial-graph packing ceiling 448->512 KiB: the grown core
graph split at the old boundary into an extra chunk, costing ~1.9 KiB of
startup gzip to compression-context resets (same documented tradeoff as
the earlier 400->448 bump). Startup lands at 313.5 KiB gzip / 9 requests,
matching the origin/main baseline, limit 315.0.
2026-07-22 10:53:22 -07:00
Peter Steinberger f0c43dcf72 feat(ui): format international phone numbers for display (#112400)
* feat(ui): format phone numbers for display

* ci: track normalization package exports in Knip
2026-07-21 17:10:25 -07:00
Peter Steinberger cad4e395d2 perf(ui): cut forced reflows in Control UI chat render path (#110472)
* perf(ui): cut forced reflows in chat render path

Profiled the Control UI with Chrome DevTools tracing against a real-data
gateway: session-switch INP was 367ms with ForcedReflow insights on every
load and interaction trace.

- chat-thread: per-row stable Lit ref callbacks (keyed by row key) so the
  virtualizer stops cache-sweeping and re-measuring every visible row on
  every render; prune callbacks when rows leave the list
- chat-composer: stable textarea ref on per-pane state instead of an
  inline arrow, so the textarea is re-measured only on attach or when the
  draft changes programmatically, not on every chat render
- app-sidebar: coalesce scrollHeight/scrollTop reads from updated() into
  one rAF per frame instead of a forced layout flush per render

After: INP 133-143ms on the same interaction sequence, no ForcedReflow
insight in load or interaction traces.

* perf(ui): keep Control UI startup under budget after reflow fixes

The reflow fixes shifted rolldown's chunk partition: the 400 KiB core
maxSize boundary split one core chunk in two (~1.4 KiB gzip compression
loss) and re-balancing minted a tiny build-info startup chunk, pushing
startup JS to 370.8 KiB over the 370 KiB budget.

- pin build-info.ts + build-info-normalizers.ts into control-ui-shared so
  partition noise stops minting extra startup preload requests
- raise core maxSize 400 -> 448 KiB so the core graph packs into fewer,
  better-compressing chunks

Startup JS: 22 requests, 369.1 KiB gzip (main: 23 requests, 369.3 KiB).
2026-07-18 08:04:03 +01:00
Peter Steinberger cf8b57e7d0 feat(skills): scan session history for workshop ideas (#106766)
* feat: scan past sessions for skill proposals

* feat(ui): add progressive skill history scans

* fix(ui): keep skill history scans synchronized

* refactor: split skill history scan ownership

* style: fix mock helper formatting

* style: format skill history scan

* build: refresh skill history schema baselines

* build: refresh plugin SDK baseline after rebase

* perf(ui): keep startup request budget bounded

* fix(skills): satisfy history scan integration gates

* fix(ui): bound control ui startup chunks

* build: refresh plugin SDK API baseline

* build(ui): refresh self-learning translation memory

* refactor(ui): split skill workshop state

* fix(ci): refresh skill workshop gates

* fix(ci): satisfy skill history lint

* build: refresh plugin SDK baseline after main rebase
2026-07-13 16:15:50 -07:00
Peter Steinberger 995c75baa0 fix(ci): restore current-main quality gates (#106751)
* fix(ci): restore Control UI quality gates

* fix(ci): repair dead export cleanup fallout

* fix(ci): repair latest main quality gates

* fix(ci): satisfy current-main gate contracts

* docs: format Cloud Workers guide

* test: isolate context lookup module
2026-07-13 13:54:03 -07:00
Vincent Koc 7e995cbf2b refactor(ui): remove stale helper exports (#101549) 2026-07-07 02:49:42 -07:00
Vincent Koc 62e5d4406d refactor(ui): remove unused module exports (#101262) 2026-07-06 20:20:06 -07:00
Peter Steinberger 12a56d4d46 docs: document control ui sources 2026-06-04 20:57:09 -04:00
Vincent Koc 968c87d798 fix(ui): move control ui chunking helper out of runtime source 2026-05-25 14:31:38 +02:00