Commit graph

5212 commits

Author SHA1 Message Date
nesquena-hermes
94513dd0f2 Release XF (v0.51.650): protect table-cell pipes inside inline code (#4896) 2026-06-25 08:37:05 +00:00
luandnh
91af4de845 fix: protect pipes inside <code> tags, not backticks
Inline code pass converts `code` to <code>...</code> BEFORE the table
pass runs, so the _protectPipes backtick regex never matched. Changed
to protect pipes inside <code>...</code> tag pairs instead.
2026-06-25 08:25:58 +00:00
luandnh
dba6340661 test: add regression test for pipe inside inline code in renderMd tables 2026-06-25 08:25:58 +00:00
luandnh
c1c039ff80 fix: protect pipes inside backtick code spans in renderMd tables
The _protectPipes function protected | inside brackets but not inside
backtick inline-code spans, so || in `code || here` inside a table
cell was split into extra columns. Added a second replace for matched
backtick pairs in the same iterative loop.
2026-06-25 08:25:58 +00:00
luandnh
f82e786c99 fix: table pipe | inside inline code breaks markdown table rendering
The smd parser treated | characters as table column separators even
when inside backtick inline code (token 11). Added e.token!==m (code
span) guards in both table | handlers so pipes inside code are
output as literal text instead of creating table cells.
2026-06-25 08:25:58 +00:00
nesquena-hermes
aaa70157fd
Release XE (v0.51.649): activate gallery-installed extensions + fix subdir asset URLs (#4907)
Release XE (v0.51.649): activate gallery-installed extensions + fix subdir asset URLs (#4907)
2026-06-25 00:24:20 -07:00
nesquena-hermes
22845e3f42 Release XE (v0.51.649): activate gallery-installed extensions + fix subdir asset URLs (#4907) 2026-06-25 07:21:25 +00:00
Frank Song
5f17ba55ed Fix gallery-installed extension activation 2026-06-25 07:10:17 +00:00
nesquena-hermes
cea9244460
Release XD (v0.51.648): restore tool-card result body on settled rebuild path (#4904, #4622)
Release XD (v0.51.648): restore tool-card result body on settled rebuild path (#4904, #4622)
2026-06-25 00:06:18 -07:00
nesquena-hermes
0f3eaa4c9a Release XD (v0.51.648): restore tool-card result body on settled rebuild path (#4904, #4622) 2026-06-25 07:03:22 +00:00
nesquena-hermes
89d7fae347 fix(#4622): restore full body for CAPPED-PREVIEW settled snippets, not just empty
Codex gate caught that the enrich only restored when the settled snippet was
fully EMPTY — but the backend persists a 4000-char capped preview
(_TOOL_RESULT_SNIPPET_MAX), so long terminal/tool output settles to a truncated
4000-char prefix, not empty (the actual #4622 symptom). Now also restore when
the settled snippet is a bounded preview of the live one (live longer + settled
is a prefix + settled length >= the 4000 cap), without clobbering a genuinely
short non-preview value. Adds capped-preview restore + short-prefix-not-clobbered
tests (proven non-vacuous).
2026-06-25 06:51:33 +00:00
nesquena-hermes
8d2d8b1f98 fix(#4622): trim dead enrichment branches per Opus gate
Opus + Codex both SAFE TO SHIP. Opus flagged two cosmetic redundancies (not
blockers): the snippet guard checked payload.result/payload.output which
_anchorSceneToolRowFromCall never sets, and the tool.result/tool.output writes
are never read by the consumer _anchorSceneToolCallFromRow (it reads
tool.snippet||payload.snippet||payload.result||payload.output). Removed both —
tool.snippet/payload.snippet is what actually restores the visible body.
Behavior-identical; 18 settled/reconnect tests still green.
2026-06-25 06:40:27 +00:00
nesquena-hermes
ef3092434f fix(#4622): restore tool-card result body on the settled rebuild path
In the Transparent Stream / Worklog view, terminal tool cards lost their stdout
and patch/edit cards lost their diff AFTER a turn settled (live cards were fine).

Root cause (proven via a node harness driving the real settled chain
_anchorSceneRowsByMessageIndex -> _anchorSceneToolRowFromCall ->
_anchorSceneToolCallFromRow): the settled rebuild builds each tool row from the
persisted messages[].tool_calls (state.db/sidecar), which can carry only a short
preview — or, on a cold/paginated load, nothing — for the result body. The full
output lives on the live S.toolCalls entry at settle time, but the dedup loop in
_anchorSceneRowsByMessageIndex SKIPPED the matching live entry (continue) instead
of restoring the missing body onto the surviving settled row. So buildToolCard
got tc.snippet='' -> empty output region, and the patch/edit diff (detected from
the snippet) never rendered. This also blanks the transparent 'Output' tab and
the 'Full' (detailed-input) tab.

Fix: new _enrichSettledToolRowBodyFromLive() restores snippet/result/output,
command, and args onto a settled row from the matched live call, ONLY when the
settled value is empty (never clobbers a genuinely persisted body). Wired into
the dedup loop: on a tid match that would have been skipped, enrich the existing
pooled row instead of dropping the live data.

The prior #4625 guard only exercised the LIVE buildToolCard path, which is why it
stayed green while the settled path regressed. New
tests/test_issue4622_settled_toolcard_body.py drives the full SETTLED chain and
asserts: terminal stdout restored + full output in data-full + transparent Output
tab non-empty; patch diff body restored + renders as a diff + Full-tab args kept;
genuine persisted body not clobbered; args restored when dropped; no-live-match
leaves the row untouched; no duplicate row after enrich. Verified to FAIL on clean
master (assertions) and pass with the fix. Also registered the new helper in the
and now references the new symbol).

Reusable proof harness saved to ~/WebUI/qa/harnesses/.

Closes #4622.
2026-06-25 06:40:27 +00:00
nesquena-hermes
a64978c7ca
Release XC (v0.51.647): restore mobile-PWA task-detail action buttons (#4891)
Release XC (v0.51.647): restore mobile-PWA task-detail action buttons (#4891)
2026-06-24 23:37:05 -07:00
nesquena-hermes
896afe4ff1 Release XC (v0.51.647): restore mobile-PWA task-detail action buttons (#4891) 2026-06-25 06:33:56 +00:00
nesquena-hermes
856aa1d48d fix(#4891): show header in read-only Memory sections + Profiles help view
Gate (Codex) caught two SILENT regressions in the original fix: explicitly
hiding the header in the 'else'/'empty' branches also hid populated read views.
- _setMemoryHeaderButtons('read') now shows the header for ALL read views
  (incl. read-only Project Context / External Notes), gating only the Edit btn.
- _renderProfileConceptHelp() now uses a new 'help' header mode (shows header,
  hides all action buttons) instead of 'empty' (which hid the help title).
Adds tests/test_issue4891_detail_header_visibility.py (proven non-vacuous).

Co-authored-by: luandnh <luandnh@users.noreply.github.com>
2026-06-25 06:20:20 +00:00
luandnh
561369a261 fix: task detail actions hidden on mobile PWA
The main-view header (and its action buttons) used a CSS :has(.main-view-title:empty)
rule to stay hidden until the title populated; on some mobile browsers / PWA
WebViews that pseudo-class doesn't re-evaluate after textContent is set
dynamically, leaving the header permanently hidden. Explicitly set header
display in the read/create/edit/closed header-button setters (tasks, skills,
memory, workspaces) instead of relying on :has() reactivity.

Co-authored-by: luandnh <luandnh@users.noreply.github.com>
2026-06-25 06:08:00 +00:00
nesquena-hermes
10b9ba7429
test: class-level regression guard for the #4856 mobile-transcript-collapse family
test: class-level regression guard for the #4856 mobile-transcript-collapse family
2026-06-24 22:54:11 -07:00
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-hermes
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 Yelverton
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 Yelverton
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 Yelverton
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-hermes
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