Commit Graph
5194 Commits
Author SHA1 Message Date
nesquena-hermes f1fee4ec7e test: remove unused 'rules' var (ruff F841 forward-gate) 2026-06-25 05:51:09 +00:00
nesquena-hermes 8e3cce8025 test: class-level regression guard for the #4856 mobile-transcript-collapse family
#4856 (mobile transcript completely unscrollable) survived multiple fixes
because regressions were guarded only by grepping one exact CSS string. This
adds an INVARIANT guard so the whole class can't recur: .messages-inner (base +
every media query) must never become a Y-axis scroll container — neither via an
explicit overflow-y scroll value nor via overflow-x:hidden/scroll/auto (which
the CSS spec coerces overflow-y->auto, collapsing min-height:auto to 0 inside
the flex column). overflow-x:clip is the only allowed horizontal clip. Also
asserts .messages stays the real scroller (overflow-y:auto + min-height:0).
Proven non-vacuous: reintroducing overflow-x:hidden on the mobile rule fails it.
For a true runtime check at a real viewport, the private workspace has
scripts/layout_scroll_probe.py.
2026-06-25 05:47:08 +00:00
nesquena-hermes 0ac52cba42 Release XB (v0.51.646): mobile transcript scrolls again — overflow-x:clip (#4898, closes #4856)
Release XB (v0.51.646): mobile transcript scrolls again — overflow-x:clip (#4898, closes #4856)
2026-06-24 22:35:26 -07:00
nesquena-hermes 4a3970f76d Release XB (v0.51.646): mobile transcript scrolls again — overflow-x:clip (#4898, closes #4856) 2026-06-25 05:19:11 +00:00
nesquena-hermes 3acd802588 fix(#4856): mobile transcript collapses unscrollable — overflow-x:hidden coerces overflow-y:auto
The mobile .messages-inner rule used overflow-x:hidden, which per the CSS
overflow spec coerces overflow-y from visible to auto — turning the inner
into a scroll container. A scroll container's min-height:auto resolves to 0,
so the inner (a flex-shrink:1 item inside the .messages column flexbox)
collapsed to the viewport height instead of growing to its content. The
transcript then overflowed the clipped inner rather than the .messages
scroller, leaving nothing to scroll and pinning scrollTop at 0 — every
open/send/receive stranded mobile users at the top with no way to scroll down.

Fix: overflow-x:clip suppresses horizontal overflow (the original #4583/#4553
intent) WITHOUT coercing overflow-y or creating a scroll container, so
min-height:auto stays min-content and the inner grows to full height.

Verified empirically via headless Chrome mobile emulation (390x844, mobile=true):
scrollableDelta 0 -> 7746; open/send/receive all land at bottom; mid-scroll
position preserved across re-render. Desktop unaffected.

Updated #4553 test (clip satisfies its horizontal-pan goal) + added dedicated
2026-06-25 05:18:32 +00:00
nesquena-hermes 690b40c99b Release XA (v0.51.645): cache sidebar-projection sidecar reads + hoist cron per-row I/O (#4889, #4842)
Release XA (v0.51.645): cache sidebar-projection sidecar reads + hoist cron per-row I/O (#4889, #4842)
2026-06-24 22:12:36 -07:00
nesquena-hermes d6e9c0692b Release XA (v0.51.645): cache sidebar-projection sidecar reads + hoist cron per-row I/O (#4889, #4842) 2026-06-25 05:09:39 +00:00
nesquena-hermes b149a975c6 fix(#4842): update #4385 archive tests to real sidecar files + document stat-gate assumption
Opus gate finding (BLOCKING): the two #4385 archive-reappears regression tests
mocked Session.load_metadata_only to return an archived stub WITHOUT writing a
real sidecar file, and never set SESSION_DIR. The new stat-gate in
_state_projection_sidecar_metadata short-circuits on SESSION_DIR/{sid}.json
before consulting metadata, so those mocks were never reached -> archived=False.

This is NOT a production regression (in prod, load_metadata_only reads exactly
SESSION_DIR/{sid}.json and returns None when absent, so archived metadata only
exists when the file does — in which case the stat() succeeds and behavior is
identical). The divergence existed only at the tests' mock boundary.

Fix: both tests now write a real archived sidecar JSON under a monkeypatched
SESSION_DIR, exercising the actual production read path (strictly better
coverage than mocking load_metadata_only). Also documented the
SESSION_DIR-as-sole-source assumption on the cache function per Opus's
forward-looking note.
2026-06-25 04:56:00 +00:00
nesquena-hermes a6d8319acb perf(#4842): cache sidebar-projection sidecar reads + hoist per-row I/O on the cron path
GET /api/sessions pinned CPU to 100% and took multiple seconds on profiles with
many cron sessions. The state.db -> sidebar projection did three pieces of
redundant per-row file I/O, in BOTH the visible pass and the higher-capped
(CRON_PROJECT_CHIP_LIMIT=200) cron-only second pass:

1. _state_projection_sidecar_metadata(sid): uncached open() + 64KB prefix read +
   JSON-key scan, once per row.
2. get_last_workspace(): up to two file reads + an is_dir()/remote probe,
   returning the SAME workspace for every row.
3. cron/jobs.json: full read + parse per untitled cron row.

On a cron-heavy install that is hundreds of file reads per build, and because
the enclosing _CLI_SESSIONS_CACHE is keyed on a state.db content fingerprint
that advances on every streamed message row, the whole scan was re-paid on
essentially every 5s poll during a live turn (the recurring 100% CPU / slow
/api/sessions from #4672 -> #4808 -> #4842).

Fix:
- Memoize _state_projection_sidecar_metadata by the sidecar's
  (path, mtime_ns, size, ctime_ns) stat signature in a bounded module cache, so
  a warm build re-stats instead of re-reads. A rename/archive/edit bumps the
  signature and invalidates just that one entry (no staleness). This survives
  the 5s CLI-cache busting during streaming, which is the actual trigger.
  Mirrors the Claude Code parse cache (#4718). Folded into
  clear_cli_sessions_cache() for explicit-reset/test parity.
- Build-scoped memos for cron jobs.json (parse once -> job_id->name map) and
  get_last_workspace() (resolve once), matching the existing _cron_pid() pattern.

Output is byte-identical. Adds tests/test_issue4842_cron_projection_perf.py
(7 tests: warm-rebuild no re-read, stat-change invalidation, dict-copy isolation,
missing-sidecar default, jobs.json once, workspace once, lifecycle clear).
2026-06-25 04:56:00 +00:00
nesquena-hermes 09b19152ab Release WZ (v0.51.644): extensions gallery browse/install/uninstall (#4879, closes #4746)
Release WZ (v0.51.644): extensions gallery browse/install/uninstall (#4879, closes #4746)
2026-06-24 21:49:39 -07:00
nesquena-hermes 7efd73432c Release WZ (v0.51.644): extensions gallery browse/install/uninstall (#4879, closes #4746) 2026-06-25 04:37:27 +00:00
nesquena-hermesandRod Boev fee11eab49 fix(#4746): make gallery Install button text theme-readable
The .extension-gallery-install-btn used color:#fff on a var(--accent) background;
with the gold accent (#FFD700 dark / #B8860B light) the white text was washed out /
low-contrast. Switched to the readable outlined treatment (accent-tinted background +
var(--accent-text) text + var(--accent) border), matching the uninstall button + the
steer-recovery-retry convention. Verified readable on Gallery + Installed tabs in
both dark and light.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-25 04:27:12 +00:00
Rod Boev 235059265b Remove unused Request import (ruff F401) 2026-06-25 04:21:50 +00:00
Rod Boev ee7ff6ed33 fix(extensions): harden gallery against live registry and security review findings 2026-06-25 04:21:50 +00:00
Rod Boev b75065e889 fix(extensions): serialize install extraction under lock and add registry cache lock (#4746) 2026-06-25 04:21:50 +00:00
Rod Boev ddd5c8d936 feat(extensions): add gallery browse, install, and uninstall (#4746) 2026-06-25 04:21:50 +00:00
nesquena-hermes ed5a217ae1 Release WX (v0.51.643): show unassigned tasks as explicit lane on mobile Kanban (#4853)
Release WX (v0.51.643): show unassigned tasks as explicit lane on mobile Kanban (#4853)
2026-06-24 21:19:58 -07:00
nesquena-hermes 7b7302bc88 fix(#4853): remove unused dataclass import (ruff F401 forward-gate) 2026-06-25 04:17:05 +00:00
nesquena-hermes 69df8d83b7 Release WX (v0.51.643): show unassigned tasks as explicit lane on mobile Kanban (#4853) 2026-06-25 04:10:46 +00:00
Brett Yelverton 2df199c554 fix(kanban): add missing vi locale keys for kanban filter i18n
The Vietnamese locale block (added upstream after the kanban-unassigned
branch was authored) was missing two i18n keys:
- kanban_tasks_hidden_by_filters
- kanban_clear_filters

These keys are required by the kanban hidden-by-filters UX which shows a
'Clear filters' button when text search hides all tasks. Both keys use
English text matching the pattern of untranslated new keys in all other
locale blocks.
2026-06-25 03:54:32 +00:00
Brett YelvertonandClaude Sonnet 4.6 dfcd94a808 fix(kanban): show unassigned tasks as explicit profile lane on mobile
- Add KANBAN_UNASSIGNED_LANE constant and _kanbanLaneKey/_kanbanLaneLabel
  helpers so unassigned tasks map to a stable internal key '__unassigned__'
  rather than a locale-dependent translated string
- _kanbanLaneNames now places 'default' first, other assignees alphabetically,
  and the Unassigned lane last
- _kanbanRenderProfileLanes filters by _kanbanLaneKey, renders label via
  _kanbanLaneLabel, and adds kanban-profile-lane-unassigned CSS class
- _kanbanTaskMeta always emits the unassigned label so sidebar shows it too
- _kanbanRenderBoard checks unfiltered total and shows a 'Tasks hidden by
  filters' state with a Clear filters button when text search hides all tasks
- Add kanban_tasks_hidden_by_filters and kanban_clear_filters i18n keys to
  all 13 locale blocks
- Add .kanban-profile-lane-unassigned header border using warning token

Fixes: on Dynasty board mobile, Ready=3 CLI but default lane showed Ready=0
because those tasks were unassigned and had no visible lane.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 03:54:32 +00:00
Brett YelvertonandClaude Sonnet 4.6 b8cf8dfc4e test(kanban): pin unassigned lane visibility in profile view
Adds three static regression tests:
- profile lanes create an explicit Unassigned lane via _kanbanLaneKey
- hidden-by-filters UX exposes clearKanbanFilters and the right i18n keys
- sidebar meta always shows the unassigned label for tasks without assignee

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 03:54:32 +00:00
Brett YelvertonandClaude Sonnet 4.6 8170acafb0 test(kanban): board payload includes unassigned ready tasks
Regression test that verifies the bridge returns unassigned Ready tasks
in the ready column when no assignee filter is active. This confirms the
API side is not the cause of the Dynasty board mobile visibility bug.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 03:54:32 +00:00
nesquena-hermes 5a46ec9bd2 Release WW (v0.51.642): steer failure reasons + Retry/Dismiss recovery bar (#4850, closes #4749)
Release WW (v0.51.642): steer failure reasons + Retry/Dismiss recovery bar (#4850, closes #4749)
2026-06-24 20:50:41 -07:00
nesquena-hermes 8599554f89 Release WW (v0.51.642): steer failure reasons + Retry/Dismiss recovery bar (#4850, closes #4749) 2026-06-25 03:47:51 +00:00
nesquena-hermesandRod Boev c315839836 fix(#4749): make steer-recovery button text theme-readable (explicit color tokens)
The .steer-recovery buttons set background:var(--surface) but no color, so native
<button> text fell back to the UA ButtonText (dark) color -> dark-on-dark unreadable
'Retry'/'Dismiss' in dark mode. Added explicit color:var(--text) on buttons + label,
styled Retry as an accent-colored primary (color+border var(--accent), bold), and
white-space:nowrap so labels don't wrap on mobile. Verified readable across dark,
light, and mobile viewports.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-25 03:37:26 +00:00
Rod Boev b70949f484 fix(steer): rename typo, catch retry rejection, update JSDoc (#4749) 2026-06-25 03:23:36 +00:00
Rod Boev d28f39a0b4 fix: add steer_fail/recovery i18n keys to all non-English locales 2026-06-25 03:23:36 +00:00
Rod Boev 5312f96746 fix: move _trySteer JSDoc above its function definition 2026-06-25 03:23:36 +00:00
Rod Boev c11049646f fix: use real CSS tokens for recovery bar and update stale test assertion 2026-06-25 03:23:36 +00:00
Rod Boev 764d5b8601 fix(#4749): show steer failure reason and offer retry/dismiss recovery 2026-06-25 03:23:36 +00:00
nesquena-hermes ff110458fd Release WV (v0.51.641): dark-mode disabled-plugin badge contrast + active-first sort (#4848, closes #4713)
Release WV (v0.51.641): dark-mode disabled-plugin badge contrast + active-first sort (#4848, closes #4713)
2026-06-24 20:20:46 -07:00
nesquena-hermes d59e60235f Release WV (v0.51.641): dark-mode disabled-plugin badge contrast + active-first sort (#4848, closes #4713) 2026-06-25 03:17:51 +00:00
Rod Boev c109ed9113 fix(plugins): align activation-state sort with badge precedence (#4713) 2026-06-25 02:57:06 +00:00
Rod Boev e3a1540d32 fix: align sort partition with badge for inactive exclusive providers 2026-06-25 02:57:06 +00:00
Rod Boev 7c1cd99961 fix(#4713): dark-mode disabled badge contrast and active-first plugin sort 2026-06-25 02:57:06 +00:00
nesquena-hermes 98e13bc92a Release WU (v0.51.640): cache Claude Code transcript parses on the profile-switch path (#4718, #4662)
Release WU (v0.51.640): cache Claude Code transcript parses on the profile-switch path (#4718, #4662)
2026-06-24 19:51:14 -07:00
nesquena-hermes f7788d9d5a Release WU (v0.51.640): cache Claude Code transcript parses on the profile-switch path (#4718, #4662) 2026-06-25 02:47:03 +00:00
nesquena-hermes d61ca37bd1 fix(#4718): harden parse-cache key with ctime_ns + preserve epoch-0 mtime fallback
Dual-gate (Codex + Opus) findings on the Claude Code transcript parse cache:

1. Cache-key staleness hardening: add st_ctime_ns to the (path, mtime_ns, size,
   max_messages) key so a rare same-size, same-mtime in-place edit can't serve a
   stale parse. Size is already the strong discriminator for append-only JSONL;
   ctime_ns is cheap defense-in-depth (a spurious bump costs one harmless re-parse,
   never a stale read).

2. Timestamp-fallback bug-for-bug fix: the row-builder guard used identity
   (first_ts is None and last_ts is None) but the field assignment uses truthiness
   (first_ts or last_ts or _mtime). They disagreed on a falsy 0.0 timestamp
   (epoch-0/1970 transcripts), leaving created_at/updated_at/last_message_at = None
   instead of the file mtime the pre-cache inline code fell back to. Guard now uses
   the same 'not first_ts and not last_ts' test.

Adds 3 regression tests: epoch-0 mtime fallback, the shared-dict read-only
contract (pins the load-bearing invariant), and same-size+mtime ctime invalidation.
2026-06-25 02:46:00 +00:00
nesquena-hermesandrodboev fdb734a6a0 perf(#4718): cache Claude Code transcript parses on the sidebar path
The /api/sessions cold build (which a profile switch triggers) was
dominated by get_claude_code_sessions() re-parsing every Claude Code
JSONL transcript line-by-line on each call — 650-1000ms on a 200-file
/ ~130MB tree, repeated on every switch, every 5s CLI-cache expiry, and
every sidebar poll, because the directory is global but the higher
session cache is keyed per active profile.

Memoize the per-file parse by (path, mtime_ns, size) so a warm build
re-stats files instead of re-parsing them; any genuine edit/append
changes the stat signature and invalidates just that file. Also hoist
the loop-invariant get_last_workspace() out of the Claude Code row loop
(it stats config + probes terminal cwd, ~200 redundant calls per build).

Measured /api/sessions cold-rebuild on a 250-session profile: ~670-880ms
-> ~100-200ms (verified in-browser). Output byte-identical.

Credit @rodboev for the browser-level profiling that isolated the real
bottleneck (the prior pre-warm hypothesis could not move it).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-25 02:45:59 +00:00
nesquena-hermes 703d44b168 Release WT (v0.51.639): persist + session-scope approval-card dismissal (#4846, closes #4754)
Release WT (v0.51.639): persist + session-scope approval-card dismissal (#4846, closes #4754)
2026-06-24 19:43:44 -07:00
nesquena-hermes 5c052b59bb Release WT (v0.51.639): persist + session-scope approval-card dismissal (#4846, closes #4754) 2026-06-25 02:40:22 +00:00
nesquena-hermesandRod Boev 0a6947c2d2 fix(#4754): namespace dismissed approvals by session (gate finding)
Codex CORE: the dismissed-set was keyed by bare approval_id, but gateway/run
sources preserve externally-supplied IDs without session namespacing — so two
sessions carrying the same approval_id collide and dismissing in session A hides
session B's still-pending approval. Fix: _approvalDismissKey(sid, approvalId)
builds a session-namespaced composite key, threaded through _isApprovalDismissed,
_markApprovalDismissed, _unmarkApprovalDismissed + all 5 call sites (showApprovalCard,
dismissApprovalCard, respondApproval, no-pending poll cleanup). Updated 4 stale
assertions to the namespaced signatures + added a functional node-driver
cross-session-collision regression (proven non-vacuous).

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-25 02:15:47 +00:00
Rod Boev 0258599a46 fix(approval): clear pending attention state on dismiss (#4754) 2026-06-25 02:04:51 +00:00
Rod Boev 2764e28b95 fix: remove unused json and re imports 2026-06-25 02:04:51 +00:00
Rod Boev 820362ca14 fix: scope un-dismiss to session-local pending, not global _approvalCurrentId 2026-06-25 02:04:51 +00:00
Rod Boev 2956804a14 fix(#4754): persist approval card dismissal across tab switches and restarts 2026-06-25 02:04:51 +00:00
nesquena-hermes b66356ed67 Release WS (v0.51.638): bound continuation-fallback sidecar reads (#4882)
Release WS (v0.51.638): bound continuation-fallback sidecar reads (#4882)
2026-06-24 19:01:23 -07:00
nesquena-hermes 04fb59ac65 Release WS (v0.51.638): bound continuation-fallback sidecar reads (#4882) 2026-06-25 01:58:16 +00:00
nesquena-hermesandstarship-s 5296b15956 fix(session): preserve 4096-CHAR prefix semantics in bounded sidecar read (gate finding)
Codex reproduced a SILENT regression: the PR's 4096-BYTE read could miss the
continuation marker the old read_text()[:4096] (4096 CHARS) found. Session.save()
writes compression_anchor_summary (ensure_ascii=False) BEFORE parent_session_id,
so a multi-byte emoji summary puts the marker at char offset ~1230 but byte
offset ~4900 — within the old 4096-char window, past a 4096-byte one → continuation
child no longer detected. Fix: read 16384 bytes (4096 chars worst-case for 4-byte
UTF-8) then slice [:4096] chars, keeping the same needle + bounded I/O. Multibyte
regression test added + proven non-vacuous; updated the read-size assertion to <=16384.

Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-06-25 01:49:11 +00:00