docs: close out task-completion safety net plan; fix stale relative links
Fixes 1-3 deployed and verified live: fresh trivial Q&A sessions now reach done immediately, and a goal-bearing session that stalled was correctly nudged by the idle sweep. Fix 4 (backfill) was replaced with deletion after the operator's call — verified against the DB first that zero knowledge notes were linked to or written by any of the 53 removed sessions, so nothing was lost. Documents the pagination gap in listSessions (hardcoded LIMIT 50, no total count) that hid 6 of those sessions from the original audit. Also fixes relative links in this plan and in the UI-review plan that broke when both moved from plans/ to plans/done/ (one directory level deeper). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,22 +1,25 @@
|
|||||||
# Task completion safety net: every live task is stuck "Running"
|
# Task completion safety net: every live task is stuck "Running"
|
||||||
|
|
||||||
Status: In Progress — 2026-07-11. Fixes 1-3 implemented, built, tested
|
Status: Done — 2026-07-12. Fixes 1-3 implemented, built, tested
|
||||||
(`go build ./...`, `go test ./cmd/nomos/...`), and committed
|
(`go build ./...`, `go test ./cmd/nomos/...`), committed (`3b9c75f`),
|
||||||
(`3b9c75f`). Not yet deployed or verified live. Fix 4 (backfill of the 50
|
deployed, and verified live (see Verification below — fresh trivial Q&A
|
||||||
already-stuck live sessions) intentionally not started — per the
|
sessions now reach `done` immediately; a goal-bearing session that went
|
||||||
implementation order below, it needs 1-3 deployed and verified first.
|
idle was correctly nudged and auto-resolved by the existing resume-failure
|
||||||
|
path). Fix 4 (backfill) was replaced with deletion — see "Fix 4, revised"
|
||||||
|
below; the original backfill-with-a-fabricated-outcome approach was never
|
||||||
|
run.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
Fix the root cause of a production-wide defect found while UI-testing
|
Fix the root cause of a production-wide defect found while UI-testing
|
||||||
[`2026-07-11-ui-review-ia-usability.md`](done/2026-07-11-ui-review-ia-usability.md):
|
[`2026-07-11-ui-review-ia-usability.md`](2026-07-11-ui-review-ia-usability.md):
|
||||||
every session on the live task board shows as "Running" forever. Traced
|
every session on the live task board shows as "Running" forever. Traced
|
||||||
through `cmd/nomos/` and confirmed against the running database — this is
|
through `cmd/nomos/` and confirmed against the running database — this is
|
||||||
not a frontend bug (the board correctly reflects real `agent_sessions.status`
|
not a frontend bug (the board correctly reflects real `agent_sessions.status`
|
||||||
values). It's an agent-behavior gap: the model almost never calls the
|
values). It's an agent-behavior gap: the model almost never calls the
|
||||||
lifecycle tools (`set_goal` / `propose_plan` / `complete_task`) that the
|
lifecycle tools (`set_goal` / `propose_plan` / `complete_task`) that the
|
||||||
task-board feature (shipped today,
|
task-board feature (shipped today,
|
||||||
[`done/2026-07-11-goal-oriented-chat-control-panel.md`](done/2026-07-11-goal-oriented-chat-control-panel.md))
|
[`done/2026-07-11-goal-oriented-chat-control-panel.md`](2026-07-11-goal-oriented-chat-control-panel.md))
|
||||||
depends on to know a task is finished.
|
depends on to know a task is finished.
|
||||||
|
|
||||||
## Evidence
|
## Evidence
|
||||||
@@ -33,7 +36,7 @@ per-session transcripts) against the running mac-mini stack:
|
|||||||
lxc:caddy?"), the model runs one read tool (`run hostname`), answers in
|
lxc:caddy?"), the model runs one read tool (`run hostname`), answers in
|
||||||
plain text, and the turn ends — no lifecycle tool call at all. This is
|
plain text, and the turn ends — no lifecycle tool call at all. This is
|
||||||
exactly the case
|
exactly the case
|
||||||
[`nomos/SOUL.md:105-109`](../nomos/SOUL.md#L105) calls out by name
|
[`nomos/SOUL.md:105-109`](../../nomos/SOUL.md#L105) calls out by name
|
||||||
("a trivial read-only task... is a degenerate case... answer it and
|
("a trivial read-only task... is a degenerate case... answer it and
|
||||||
`complete_task` with a one-line summary") — the instruction exists and is
|
`complete_task` with a one-line summary") — the instruction exists and is
|
||||||
explicit, and the model skips it anyway, consistently.
|
explicit, and the model skips it anyway, consistently.
|
||||||
@@ -74,7 +77,7 @@ gap.
|
|||||||
Neither exit currently checks whether the session ever reached a terminal
|
Neither exit currently checks whether the session ever reached a terminal
|
||||||
state — the turn just ends, and `agent_sessions.status` is left wherever it
|
state — the turn just ends, and `agent_sessions.status` is left wherever it
|
||||||
was (usually `active`, its creation-time default,
|
was (usually `active`, its creation-time default,
|
||||||
[`store.go:71,84`](../cmd/nomos/store.go#L71)).
|
[`store.go:71,84`](../../cmd/nomos/store.go#L71)).
|
||||||
|
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||
@@ -92,7 +95,7 @@ answered. Call `store.completeTask` server-side right there, before the
|
|||||||
`return`, with `outcome="success"` and a summary derived from the response
|
`return`, with `outcome="success"` and a summary derived from the response
|
||||||
text (first ~120 chars, same truncation pattern
|
text (first ~120 chars, same truncation pattern
|
||||||
`buildContinuationNote` already uses at
|
`buildContinuationNote` already uses at
|
||||||
[`continue.go:203-205`](../cmd/nomos/continue.go#L203)). No LLM call needed
|
[`continue.go:203-205`](../../cmd/nomos/continue.go#L203)). No LLM call needed
|
||||||
— this is a mechanical default, not a judgment call, matching the "trivial
|
— this is a mechanical default, not a judgment call, matching the "trivial
|
||||||
task" case SOUL.md already describes.
|
task" case SOUL.md already describes.
|
||||||
|
|
||||||
@@ -105,7 +108,7 @@ covers them.
|
|||||||
**2. Structured (goal/plan set) sessions that stall — idle sweep, not
|
**2. Structured (goal/plan set) sessions that stall — idle sweep, not
|
||||||
inline.**
|
inline.**
|
||||||
Extend the existing `runContinuationWorker` ticker
|
Extend the existing `runContinuationWorker` ticker
|
||||||
([`continue.go:41-57`](../cmd/nomos/continue.go#L41), already polling every
|
([`continue.go:41-57`](../../cmd/nomos/continue.go#L41), already polling every
|
||||||
4s for a different purpose) with a second, coarser sweep — e.g. every 5
|
4s for a different purpose) with a second, coarser sweep — e.g. every 5
|
||||||
minutes — that finds sessions where:
|
minutes — that finds sessions where:
|
||||||
- `status` is `active`, `planning`, or `executing` (not already terminal or
|
- `status` is `active`, `planning`, or `executing` (not already terminal or
|
||||||
@@ -121,7 +124,7 @@ session ("[System: this task has been idle for N minutes with no
|
|||||||
you're genuinely still working, ignore this.]") the same way
|
you're genuinely still working, ignore this.]") the same way
|
||||||
`buildContinuationNote` already injects notes into resumed sessions — reuse
|
`buildContinuationNote` already injects notes into resumed sessions — reuse
|
||||||
`resumeSession`'s live-persist pattern
|
`resumeSession`'s live-persist pattern
|
||||||
([`continue.go:106-196`](../cmd/nomos/continue.go#L106)) so the nudge and
|
([`continue.go:106-196`](../../cmd/nomos/continue.go#L106)) so the nudge and
|
||||||
the model's response show up in the transcript, not silently.
|
the model's response show up in the transcript, not silently.
|
||||||
|
|
||||||
If a second idle sweep finds the same session still not completed (i.e.
|
If a second idle sweep finds the same session still not completed (i.e.
|
||||||
@@ -130,7 +133,7 @@ the nudge didn't take), auto-complete it directly with
|
|||||||
unanswered nudge — same reasoning as `resumeSession`'s existing
|
unanswered nudge — same reasoning as `resumeSession`'s existing
|
||||||
"give the task a real, operator-visible terminal state instead of leaving
|
"give the task a real, operator-visible terminal state instead of leaving
|
||||||
it silently stuck forever" logic at
|
it silently stuck forever" logic at
|
||||||
[`continue.go:179-193`](../cmd/nomos/continue.go#L179), which already does
|
[`continue.go:179-193`](../../cmd/nomos/continue.go#L179), which already does
|
||||||
exactly this for a different failure mode (a resume that produces no
|
exactly this for a different failure mode (a resume that produces no
|
||||||
response). This is the same architectural pattern, applied to a session
|
response). This is the same architectural pattern, applied to a session
|
||||||
that produces responses but never a terminal tool call.
|
that produces responses but never a terminal tool call.
|
||||||
@@ -165,6 +168,51 @@ through with no lifecycle signal at all.
|
|||||||
of new correctly-terminating ones. This should be a script, not a manual
|
of new correctly-terminating ones. This should be a script, not a manual
|
||||||
UPDATE — the classification logic will already exist in Go.
|
UPDATE — the classification logic will already exist in Go.
|
||||||
|
|
||||||
|
## Fix 4, revised: deletion instead of backfill
|
||||||
|
|
||||||
|
The plan as written proposed backfilling the 50 already-stuck sessions with
|
||||||
|
a mechanically-assigned outcome (`success` for case 1, `partial` for case
|
||||||
|
2). When it came time to execute that, the operator raised a better
|
||||||
|
question: these were overwhelmingly one-off test/smoke-test sessions
|
||||||
|
("hi", "what's the hostname of lxc:caddy?") with no lasting value —
|
||||||
|
assigning them a fabricated `success` outcome would make the task board
|
||||||
|
lie in the opposite direction (claiming verified success on things nobody
|
||||||
|
verified). The operator's call: delete them instead of backfilling a
|
||||||
|
guessed outcome, with one condition — don't lose any recorded knowledge.
|
||||||
|
|
||||||
|
Before deleting anything, verified directly against the database (not
|
||||||
|
assumed from reading the code):
|
||||||
|
- Zero `documents` relationship edges exist linking any of the candidate
|
||||||
|
sessions to any `knowledge_entities` row.
|
||||||
|
- Zero `upsert_knowledge` calls appear anywhere in the candidate sessions'
|
||||||
|
transcripts.
|
||||||
|
- Zero `knowledge_entities` rows exist system-wide mentioning the one
|
||||||
|
topic (`typetype`) the operator specifically asked to preserve.
|
||||||
|
|
||||||
|
`deleteSession` (`store.go:293`, already the live code path behind the
|
||||||
|
UI's "Delete task" button — reused as-is, not reimplemented) removes the
|
||||||
|
session, its messages, its own task entity, and that entity's relationship
|
||||||
|
edges — it never touches `knowledge_entities` rows or entities the task
|
||||||
|
merely referenced (e.g. `lxc:typetype` itself), only the provenance edges
|
||||||
|
back to the now-deleted task. Given the verification above, this was safe:
|
||||||
|
there was nothing to preserve because nothing had ever been recorded.
|
||||||
|
|
||||||
|
Executed in two batches, both via the same `DELETE /sessions/:id` route:
|
||||||
|
- **47 sessions** — the original candidate set from `curl
|
||||||
|
localhost:8092/sessions`, all non-`done`/`failed` at the time.
|
||||||
|
- **6 more sessions** — found *after* the first batch, when they surfaced
|
||||||
|
on the task board: `listSessions` (`store.go:193`) hardcodes
|
||||||
|
`ORDER BY last_active_at DESC LIMIT 50` with no pagination, so the
|
||||||
|
original audit's "50 sessions total" was actually "the 50 most
|
||||||
|
recently active" — it silently excluded 6 older stuck sessions from
|
||||||
|
2026-07-08 (predating the task-board feature entirely, same trivial
|
||||||
|
"hi"/smoke-test pattern). Worth knowing about `listSessions`'s cap for
|
||||||
|
any future audit of this table — a `count(*)` query directly against
|
||||||
|
the database is the only way to get a true total.
|
||||||
|
|
||||||
|
Final state: `agent_sessions` holds exactly 3 rows — the two `done` and
|
||||||
|
one `failed` sessions produced during live verification of fixes 1-3.
|
||||||
|
|
||||||
## Implementation order
|
## Implementation order
|
||||||
|
|
||||||
1. Fix 1 (inline safety net) first — it's the highest-leverage, lowest-risk
|
1. Fix 1 (inline safety net) first — it's the highest-leverage, lowest-risk
|
||||||
@@ -25,13 +25,13 @@ structure — implement on a later "proceed."
|
|||||||
### A. Information architecture
|
### A. Information architecture
|
||||||
|
|
||||||
**A1. Entity detail has two competing UI patterns for the same content.**
|
**A1. Entity detail has two competing UI patterns for the same content.**
|
||||||
[`Entities.svelte:16-19,155`](../web/src/pages/Entities.svelte) opens entity
|
[`Entities.svelte:16-19,155`](../../web/src/pages/Entities.svelte) opens entity
|
||||||
detail as an in-page `EntitySheet` slide-over (no URL change, no sidebar
|
detail as an in-page `EntitySheet` slide-over (no URL change, no sidebar
|
||||||
state change). [`Knowledge.svelte:57-59`](../web/src/pages/Knowledge.svelte)
|
state change). [`Knowledge.svelte:57-59`](../../web/src/pages/Knowledge.svelte)
|
||||||
and [`Graph.svelte:464`](../web/src/pages/Graph.svelte) instead navigate via
|
and [`Graph.svelte:464`](../../web/src/pages/Graph.svelte) instead navigate via
|
||||||
`location.hash = '#/entity/' + slug`, which `App.svelte`'s router resolves to
|
`location.hash = '#/entity/' + slug`, which `App.svelte`'s router resolves to
|
||||||
a full-page `EntityDetail` route — but `'entity'` isn't in `navItems`
|
a full-page `EntityDetail` route — but `'entity'` isn't in `navItems`
|
||||||
([`App.svelte:68-79`](../web/src/App.svelte)), so landing there leaves the
|
([`App.svelte:68-79`](../../web/src/App.svelte)), so landing there leaves the
|
||||||
sidebar with nothing highlighted and the header showing the raw slug instead
|
sidebar with nothing highlighted and the header showing the raw slug instead
|
||||||
of a section name. Same underlying view
|
of a section name. Same underlying view
|
||||||
(`EntityDetailContent.svelte`), three different entry points, two
|
(`EntityDetailContent.svelte`), three different entry points, two
|
||||||
@@ -42,8 +42,8 @@ A user who reaches an entity via Knowledge or Graph has no way back to
|
|||||||
**A2. Two chat entry points with no visual link between them.**
|
**A2. Two chat entry points with no visual link between them.**
|
||||||
The sidebar's "Tasks" section (board → `Chat.svelte` detail,
|
The sidebar's "Tasks" section (board → `Chat.svelte` detail,
|
||||||
`isActive={page === 'tasks' || page === 'chat'}`,
|
`isActive={page === 'tasks' || page === 'chat'}`,
|
||||||
[`App.svelte:127`](../web/src/App.svelte)) and the footer's "Chat drawer"
|
[`App.svelte:127`](../../web/src/App.svelte)) and the footer's "Chat drawer"
|
||||||
button ([`App.svelte:160-163`](../web/src/App.svelte), opens a `Sheet`
|
button ([`App.svelte:160-163`](../../web/src/App.svelte), opens a `Sheet`
|
||||||
wrapping the same `Chat` component) are both valid, intentional ways to
|
wrapping the same `Chat` component) are both valid, intentional ways to
|
||||||
reach chat — but nothing in the UI explains they're different modes (drawer
|
reach chat — but nothing in the UI explains they're different modes (drawer
|
||||||
= overlay on current page, keeps your place; Tasks = full navigation). A
|
= overlay on current page, keeps your place; Tasks = full navigation). A
|
||||||
@@ -51,10 +51,10 @@ first-time user has no way to know which one preserves their current page.
|
|||||||
Low-severity, but worth a tooltip/label distinction.
|
Low-severity, but worth a tooltip/label distinction.
|
||||||
|
|
||||||
**A3. Overview's KPI cards don't drill down.**
|
**A3. Overview's KPI cards don't drill down.**
|
||||||
[`Overview.svelte`](../web/src/pages/Overview.svelte) shows "Pending
|
[`Overview.svelte`](../../web/src/pages/Overview.svelte) shows "Pending
|
||||||
approvals," "Open signals," and fleet-health counts as static cards. The
|
approvals," "Open signals," and fleet-health counts as static cards. The
|
||||||
header badges for the same data (`approvalsPending`, `openSignals`,
|
header badges for the same data (`approvalsPending`, `openSignals`,
|
||||||
[`App.svelte:185-194`](../web/src/App.svelte)) ARE clickable and navigate to
|
[`App.svelte:185-194`](../../web/src/App.svelte)) ARE clickable and navigate to
|
||||||
Ops/Signals — so the pattern exists in the app, just not on the page whose
|
Ops/Signals — so the pattern exists in the app, just not on the page whose
|
||||||
entire purpose is summarizing this data. A dashboard card showing a count
|
entire purpose is summarizing this data. A dashboard card showing a count
|
||||||
that doesn't lead anywhere is a standard drill-down gap.
|
that doesn't lead anywhere is a standard drill-down gap.
|
||||||
@@ -63,17 +63,17 @@ that doesn't lead anywhere is a standard drill-down gap.
|
|||||||
|
|
||||||
**B1. Table-row click targets lack keyboard/screen-reader support in one
|
**B1. Table-row click targets lack keyboard/screen-reader support in one
|
||||||
place but not others.**
|
place but not others.**
|
||||||
[`Entities.svelte:117-120`](../web/src/pages/Entities.svelte) makes an
|
[`Entities.svelte:117-120`](../../web/src/pages/Entities.svelte) makes an
|
||||||
entire `Table.Row` clickable via a bare `onclick`, with no `role`,
|
entire `Table.Row` clickable via a bare `onclick`, with no `role`,
|
||||||
`tabindex`, or `onkeydown` — unreachable and inoperable via keyboard, and
|
`tabindex`, or `onkeydown` — unreachable and inoperable via keyboard, and
|
||||||
screen readers get no indication the row is interactive. This is a
|
screen readers get no indication the row is interactive. This is a
|
||||||
regression against the codebase's own established pattern: `Tasks.svelte`
|
regression against the codebase's own established pattern: `Tasks.svelte`
|
||||||
wraps its cards in real `<button>` elements
|
wraps its cards in real `<button>` elements
|
||||||
([`Tasks.svelte:172`](../web/src/pages/Tasks.svelte)), `Events.svelte`'s
|
([`Tasks.svelte:172`](../../web/src/pages/Tasks.svelte)), `Events.svelte`'s
|
||||||
correlation-group headers are real `<button>`s
|
correlation-group headers are real `<button>`s
|
||||||
([`Events.svelte:110-114`](../web/src/pages/Events.svelte)), and
|
([`Events.svelte:110-114`](../../web/src/pages/Events.svelte)), and
|
||||||
`Graph.svelte`'s SVG nodes explicitly add `role="button"`, `tabindex="0"`,
|
`Graph.svelte`'s SVG nodes explicitly add `role="button"`, `tabindex="0"`,
|
||||||
and `onkeydown` ([`Graph.svelte:416-421`](../web/src/pages/Graph.svelte)).
|
and `onkeydown` ([`Graph.svelte:416-421`](../../web/src/pages/Graph.svelte)).
|
||||||
Entities is the outlier.
|
Entities is the outlier.
|
||||||
|
|
||||||
**B2. Filter inputs are inconsistently "live" vs. "apply-on-blur," with no
|
**B2. Filter inputs are inconsistently "live" vs. "apply-on-blur," with no
|
||||||
@@ -81,10 +81,10 @@ visual cue either way.**
|
|||||||
`Entities.svelte`'s slug/name filter and `Graph.svelte`'s search box filter
|
`Entities.svelte`'s slug/name filter and `Graph.svelte`'s search box filter
|
||||||
as-you-type (bound to a `$derived`). But `Ops.svelte` (implicitly, no text
|
as-you-type (bound to a `$derived`). But `Ops.svelte` (implicitly, no text
|
||||||
filters), `Audit.svelte`'s action/entity inputs
|
filters), `Audit.svelte`'s action/entity inputs
|
||||||
([`Audit.svelte:71-72`](../web/src/pages/Audit.svelte)),
|
([`Audit.svelte:71-72`](../../web/src/pages/Audit.svelte)),
|
||||||
`Agent.svelte`'s agent_id input
|
`Agent.svelte`'s agent_id input
|
||||||
([`Agent.svelte:59`](../web/src/pages/Agent.svelte)), and `Events.svelte`'s
|
([`Agent.svelte:59`](../../web/src/pages/Agent.svelte)), and `Events.svelte`'s
|
||||||
type/severity inputs ([`Events.svelte:92-93`](../web/src/pages/Events.svelte))
|
type/severity inputs ([`Events.svelte:92-93`](../../web/src/pages/Events.svelte))
|
||||||
all use `onchange`, which only fires on blur — a user typing a filter value
|
all use `onchange`, which only fires on blur — a user typing a filter value
|
||||||
and watching the table sees nothing happen until they click or tab away, and
|
and watching the table sees nothing happen until they click or tab away, and
|
||||||
nothing in the UI (placeholder text, a debounce spinner, an "Enter to
|
nothing in the UI (placeholder text, a debounce spinner, an "Enter to
|
||||||
@@ -93,10 +93,10 @@ apply" hint) tells them why. Three different pages share the same
|
|||||||
it reads as broken on first use.
|
it reads as broken on first use.
|
||||||
|
|
||||||
**B3. Entity filter is case-sensitive; nothing else in the app is.**
|
**B3. Entity filter is case-sensitive; nothing else in the app is.**
|
||||||
[`Entities.svelte:50`](../web/src/pages/Entities.svelte) matches with raw
|
[`Entities.svelte:50`](../../web/src/pages/Entities.svelte) matches with raw
|
||||||
`.includes()`, no `.toLowerCase()`. `Graph.svelte`'s equivalent search
|
`.includes()`, no `.toLowerCase()`. `Graph.svelte`'s equivalent search
|
||||||
normalizes both sides
|
normalizes both sides
|
||||||
([`Graph.svelte:175-176`](../web/src/pages/Graph.svelte):
|
([`Graph.svelte:175-176`](../../web/src/pages/Graph.svelte):
|
||||||
`n.slug.toLowerCase().includes(q)`). Slugs are lowercase by convention today,
|
`n.slug.toLowerCase().includes(q)`). Slugs are lowercase by convention today,
|
||||||
which is why this hasn't bitten anyone yet, but entity *names* are
|
which is why this hasn't bitten anyone yet, but entity *names* are
|
||||||
free text and can be mixed-case — a name filter that silently returns zero
|
free text and can be mixed-case — a name filter that silently returns zero
|
||||||
@@ -105,7 +105,7 @@ one-line fix already has a working reference implementation three files
|
|||||||
away.
|
away.
|
||||||
|
|
||||||
**B4. `{@html}` on server-provided search snippets.**
|
**B4. `{@html}` on server-provided search snippets.**
|
||||||
[`Knowledge.svelte:120-121`](../web/src/pages/Knowledge.svelte) renders
|
[`Knowledge.svelte:120-121`](../../web/src/pages/Knowledge.svelte) renders
|
||||||
`hit.snippet` with `{@html}`, justified by a comment claiming the backend's
|
`hit.snippet` with `{@html}`, justified by a comment claiming the backend's
|
||||||
`ts_headline` output is pre-sanitized. That's true for Postgres
|
`ts_headline` output is pre-sanitized. That's true for Postgres
|
||||||
`ts_headline` today (it only wraps matched terms in `<b>` from a
|
`ts_headline` today (it only wraps matched terms in `<b>` from a
|
||||||
@@ -119,7 +119,7 @@ comment to hold forever.
|
|||||||
### C. Accessibility
|
### C. Accessibility
|
||||||
|
|
||||||
**C1. `SessionRail.svelte`'s delete control is a `<span>`, not a button.**
|
**C1. `SessionRail.svelte`'s delete control is a `<span>`, not a button.**
|
||||||
[`SessionRail.svelte:54-64`](../web/src/lib/components/SessionRail.svelte)
|
[`SessionRail.svelte:54-64`](../../web/src/lib/components/SessionRail.svelte)
|
||||||
attaches `onclick` to a `<span>` for the per-session delete affordance, with
|
attaches `onclick` to a `<span>` for the per-session delete affordance, with
|
||||||
no `role`, `tabindex`, or keyboard handler — same defect class as B1, on a
|
no `role`, `tabindex`, or keyboard handler — same defect class as B1, on a
|
||||||
destructive action this time (delete a chat session), which makes it a
|
destructive action this time (delete a chat session), which makes it a
|
||||||
@@ -143,7 +143,7 @@ C1/B1).
|
|||||||
**D1. One page bypasses the shared `Button` component.**
|
**D1. One page bypasses the shared `Button` component.**
|
||||||
`Agent.svelte`'s "Refresh" control is a bare
|
`Agent.svelte`'s "Refresh" control is a bare
|
||||||
`<button class="rounded-md border px-3 py-1.5 text-xs">`
|
`<button class="rounded-md border px-3 py-1.5 text-xs">`
|
||||||
([`Agent.svelte:73`](../web/src/pages/Agent.svelte)) instead of
|
([`Agent.svelte:73`](../../web/src/pages/Agent.svelte)) instead of
|
||||||
`Button` (`variant="outline"`), which every other page's refresh/action
|
`Button` (`variant="outline"`), which every other page's refresh/action
|
||||||
buttons use (`Ops.svelte`, `Signals.svelte`, `Audit.svelte`, `Events.svelte`
|
buttons use (`Ops.svelte`, `Signals.svelte`, `Audit.svelte`, `Events.svelte`
|
||||||
all use `<Button variant="outline">`). Cosmetically near-identical today
|
all use `<Button variant="outline">`). Cosmetically near-identical today
|
||||||
@@ -151,7 +151,7 @@ all use `<Button variant="outline">`). Cosmetically near-identical today
|
|||||||
tokens on `Button` change, since this one doesn't inherit them.
|
tokens on `Button` change, since this one doesn't inherit them.
|
||||||
|
|
||||||
**D2. `formatEventLabel` is a needless indirection.**
|
**D2. `formatEventLabel` is a needless indirection.**
|
||||||
[`Overview.svelte`](../web/src/pages/Overview.svelte)'s
|
[`Overview.svelte`](../../web/src/pages/Overview.svelte)'s
|
||||||
`formatEventLabel(ev)` returns `ev.type` verbatim — a one-line wrapper with
|
`formatEventLabel(ev)` returns `ev.type` verbatim — a one-line wrapper with
|
||||||
no formatting logic. Trivial, but noted since it reads as if formatting
|
no formatting logic. Trivial, but noted since it reads as if formatting
|
||||||
were intended and never finished.
|
were intended and never finished.
|
||||||
@@ -174,7 +174,7 @@ Priority order, grounded in user impact:
|
|||||||
action that's currently unreachable by keyboard at all. Swap the `<span>`
|
action that's currently unreachable by keyboard at all. Swap the `<span>`
|
||||||
for a real `<button>` with `aria-label="Delete session"`, matching the
|
for a real `<button>` with `aria-label="Delete session"`, matching the
|
||||||
pattern `Tasks.svelte` already uses for its own delete affordance
|
pattern `Tasks.svelte` already uses for its own delete affordance
|
||||||
([`Tasks.svelte:195-207`](../web/src/pages/Tasks.svelte) — same feature,
|
([`Tasks.svelte:195-207`](../../web/src/pages/Tasks.svelte) — same feature,
|
||||||
done correctly, in the same codebase).
|
done correctly, in the same codebase).
|
||||||
2. **B1 (Entities row click)** — wrap row content in a `<button>` (or add
|
2. **B1 (Entities row click)** — wrap row content in a `<button>` (or add
|
||||||
`role="button" tabindex="0" onkeydown`) matching `Tasks.svelte` /
|
`role="button" tabindex="0" onkeydown`) matching `Tasks.svelte` /
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ went sideways, open an investigation.
|
|||||||
| 2026-07-08 | [Liveness, drift, and UX cohesion](2026-07-08-liveness-drift-and-ux-cohesion.md) | In Progress — Phase 5 deferred |
|
| 2026-07-08 | [Liveness, drift, and UX cohesion](2026-07-08-liveness-drift-and-ux-cohesion.md) | In Progress — Phase 5 deferred |
|
||||||
| 2026-07-10 | [General gated execution: unlimited actions, gated by risk](2026-07-10-general-gated-execution.md) | In Progress — enum retirement + auto-act revival still open |
|
| 2026-07-10 | [General gated execution: unlimited actions, gated by risk](2026-07-10-general-gated-execution.md) | In Progress — enum retirement + auto-act revival still open |
|
||||||
| 2026-07-11 | [Nomos agent code review: gaps and improvement plan](2026-07-11-nomos-agent-code-review.md) | In Progress — only C1 (unauthenticated nomos gateway) still open, deferred |
|
| 2026-07-11 | [Nomos agent code review: gaps and improvement plan](2026-07-11-nomos-agent-code-review.md) | In Progress — only C1 (unauthenticated nomos gateway) still open, deferred |
|
||||||
| 2026-07-11 | [Task completion safety net: every live task is stuck "Running"](2026-07-11-task-completion-safety-net.md) | Planned — not started |
|
|
||||||
|
|
||||||
## Done
|
## Done
|
||||||
|
|
||||||
@@ -43,6 +42,7 @@ See [`done/`](done/) for executed plans:
|
|||||||
| 2026-07-11 | [Tasks: the chat page as goal-structured autonomous work](done/2026-07-11-goal-oriented-chat-control-panel.md) |
|
| 2026-07-11 | [Tasks: the chat page as goal-structured autonomous work](done/2026-07-11-goal-oriented-chat-control-panel.md) |
|
||||||
| 2026-07-11 | [Concurrent task execution: safety + throughput + frontend correctness](done/2026-07-11-concurrent-task-execution.md) |
|
| 2026-07-11 | [Concurrent task execution: safety + throughput + frontend correctness](done/2026-07-11-concurrent-task-execution.md) |
|
||||||
| 2026-07-11 | [UI review: information architecture, usability, and best practices](done/2026-07-11-ui-review-ia-usability.md) |
|
| 2026-07-11 | [UI review: information architecture, usability, and best practices](done/2026-07-11-ui-review-ia-usability.md) |
|
||||||
|
| 2026-07-11 | [Task completion safety net: every live task is stuck "Running"](done/2026-07-11-task-completion-safety-net.md) |
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user