instar 0.28.57 → 0.28.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,132 @@
1
+ # Side-Effects Review — Dashboard "Resume live output" reliability + always-visible while paused
2
+
3
+ **Version / slug:** `dashboard-resume-live-scroll`
4
+ **Date:** `2026-04-18`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required — pure UI timing fix, no gate/sentinel/watchdog/session-lifecycle surface`
7
+
8
+ ## Summary of the change
9
+
10
+ The dashboard's "Resume live output" button (and the two sibling resume paths that
11
+ fire on scroll-to-bottom and wheel-down) did not reliably snap the xterm viewport
12
+ to the bottom after being clicked. The user still landed mid-history and had to
13
+ scroll manually to see live output — defeating the purpose of the button.
14
+
15
+ Root cause: `term.write(data)` is asynchronous — xterm parses and renders the
16
+ write on a microtask. The old code called `term.scrollToBottom()` either
17
+ immediately after the write or inside a `requestAnimationFrame`. Neither
18
+ guarantees the buffer has been populated, so `scrollToBottom()` snapped against
19
+ a stale `baseY` and landed above the new output.
20
+
21
+ Fix: use xterm's write-completion callback — `term.write(data, () => { term.scrollToBottom(); })`.
22
+ The callback fires after the write is flushed, so the scroll operates on the
23
+ final buffer state.
24
+
25
+ ## Secondary change — button visibility mirrors follow state
26
+
27
+ The prior behavior only showed the "▼ Resume live output" button when new
28
+ output happened to arrive while the user was scrolled up — via `showResumeButton()`
29
+ inside `renderTerminalOutput`'s not-following branch. If the user scrolled up in
30
+ an idle session (no new output arriving), the button never appeared, and there
31
+ was no UI control to jump back to live.
32
+
33
+ Fix: `term.onScroll` now surfaces the button the moment the user scrolls up
34
+ (`!atBottom && userIsFollowing` → flip false + `showResumeButton()`), and hides
35
+ it the moment they return to the bottom by any means (`atBottom && !userIsFollowing`
36
+ → flip true + `hideResumeButton()`). The wheel-down resume path hides the button
37
+ for the same reason. Session-switch reset calls `hideResumeButton()` so a
38
+ carry-over button from a prior session doesn't mislead.
39
+
40
+ The button is now the always-available "take me back to live" control, not a
41
+ contingent reaction to incoming output.
42
+
43
+ Files touched:
44
+
45
+ - `dashboard/index.html` — two overlapping changes:
46
+ - Three resume paths use xterm's write-completion callback:
47
+ 1. The button `onclick` handler.
48
+ 2. The `term.onScroll` handler's auto-resume branch.
49
+ 3. The `xtermViewport` wheel handler's auto-resume branch.
50
+ The button handler retains `container.scrollIntoView({block:'end'})` in a
51
+ shared `snapToBottom` closure so both the pending-data and no-pending-data
52
+ branches do the same thing.
53
+ - Button visibility now mirrors `!userIsFollowing` from every path that
54
+ flips the flag: scroll-up in `onScroll`, scroll-to-bottom in `onScroll`,
55
+ wheel-down resume, and session-switch reset.
56
+ - `tests/unit/dashboard-resumeLive.test.ts` — new. HTML-inspection regression
57
+ test, consistent with the existing `dashboard-*.test.ts` pattern. Ten tests,
58
+ split across two describe blocks: (a) scroll-after-write invariants — locks
59
+ in the callback form on all three paths and bans the old
60
+ `term.write(data); requestAnimationFrame(() => term.scrollToBottom())` shape;
61
+ (b) button-visibility invariants — asserts show on scroll-up, hide on
62
+ scroll-to-bottom / wheel-resume / session switch.
63
+
64
+ ## 1. Over-block
65
+
66
+ N/A — no block/allow decision exists in this change. The only behavior altered
67
+ is the timing of a DOM scroll call relative to an xterm write.
68
+
69
+ ## 2. Under-block
70
+
71
+ N/A — see Over-block.
72
+
73
+ ## 3. Level-of-abstraction fit
74
+
75
+ The fix lives at the exact layer where the bug lives: dashboard JavaScript
76
+ interacting with xterm.js. xterm's documented API for scheduling work after
77
+ a write is the write callback; using `requestAnimationFrame` was a workaround
78
+ that guessed at timing instead of using the library's native hook. The fix
79
+ moves us from guessing to the library-native pattern.
80
+
81
+ There is no higher-level authority that should own terminal-viewport scrolling
82
+ — this is a leaf UI behavior. There is no lower layer to delegate to — xterm
83
+ itself is the layer.
84
+
85
+ ## 4. Signal-vs-authority compliance
86
+
87
+ No decision point touched. This is not a gate, filter, sentinel, watchdog, or
88
+ dispatcher. No brittle logic holds blocking authority. The principle does not
89
+ apply. Documented in Phase 1.
90
+
91
+ ## 5. Interactions
92
+
93
+ - **`renderTerminalOutput` (the steady-state write path):** Still uses the
94
+ bare `term.write(data); term.scrollToBottom();` form. That path runs only
95
+ when `userIsFollowing === true` — xterm's default behavior is to auto-track
96
+ the bottom when the viewport is already there, so the follow-up
97
+ `scrollToBottom()` is essentially redundant and harmless. Not touched.
98
+ - **`term.onScroll` flag-flipping:** The scroll event fires during
99
+ `term.write()` as the buffer grows. If the write callback runs after
100
+ `scrollToBottom()`, the onScroll handler sees `atBottom === true` and
101
+ keeps `userIsFollowing === true`. No race with flag-flipping introduced.
102
+ - **Concurrent WS output:** If a new frame arrives via WebSocket during the
103
+ click handler, it calls `renderTerminalOutput` with `userIsFollowing === true`
104
+ (we just set it). That path does its own `term.clear()` + `term.write` +
105
+ `scrollToBottom()`, which will overwrite whatever our click handler was
106
+ mid-flight. Net effect: user ends up at bottom either way. No deadlock, no
107
+ torn state.
108
+ - **Infinite-scroll history loader:** Unaffected — uses `buf.viewportY <= 10`,
109
+ which is a separate concern from the bottom-snap logic.
110
+
111
+ ## 6. External surfaces
112
+
113
+ None. This is client-side JavaScript in the dashboard HTML, served to the
114
+ user's browser. No other agents, no other users, no other systems observe the
115
+ change. No server API touched. No persisted state touched.
116
+
117
+ ## 7. Rollback cost
118
+
119
+ Trivial: `git revert <commit>`. No data migration, no state repair, no release
120
+ coordination. Dashboard is static HTML served by the instar server; a revert
121
+ commit + server restart puts the old code back in front of the user
122
+ immediately.
123
+
124
+ ## Verification
125
+
126
+ - HTML-inspection regression test passing (`tests/unit/dashboard-resumeLive.test.ts`, 6 tests).
127
+ - Full dashboard test suite passing (`dashboard-*.test.ts`, 20 tests total).
128
+ - Manual browser verification: deferred to user acceptance — reproducing the
129
+ bug end-to-end requires a live session with pending output and a user
130
+ scrolled up, which is Justin's original reporting path. The mechanical fix
131
+ is narrow enough (swap `term.write(data); scroll` → `term.write(data, scroll)`)
132
+ that the HTML regression is the load-bearing guarantee.
@@ -0,0 +1,75 @@
1
+ # Side-Effects Review — Initiative Tracker core + API
2
+
3
+ **Version / slug:** `initiative-tracker-core-and-api`
4
+ **Date:** `2026-04-17`
5
+ **Author:** `Echo (instar-developing agent)`
6
+ **Second-pass reviewer:** `not required — new opt-in persistence surface behind its own routes; no existing behavior is modified`
7
+
8
+ ## Summary of the change
9
+
10
+ Introduces a long-running-work tracker to close the user-feedback gap where multi-phase efforts (e.g. the PR-REVIEW-HARDENING rollout) stall silently because there is no structural place to persist "this is phase N of M, last touched on D, needs user decision X." Existing primitives (AttentionItem = single actionable; Job = recurring cron; Dispatch = broadcast) don't cover this shape.
11
+
12
+ Files added/modified:
13
+ - **new** `src/core/InitiativeTracker.ts` — class with CRUD + phase transitions + digest scan; atomic-write persistence to `.instar/initiatives.json` following the existing AttentionItem pattern.
14
+ - **new** `tests/unit/InitiativeTracker.test.ts` — 28 unit tests (CRUD, validation, phase transitions, persistence, digest scan).
15
+ - **new** `tests/unit/routes-initiatives.test.ts` — 15 integration tests hitting real Express with real persistence.
16
+ - **mod** `src/server/AgentServer.ts` — adds `initiativeTracker` to constructor options and routeContext.
17
+ - **mod** `src/server/routes.ts` — RouteContext field + 7 new endpoints: `GET/POST /initiatives`, `GET /initiatives/digest`, `GET/PATCH/DELETE /initiatives/:id`, `POST /initiatives/:id/phase/:phaseId`.
18
+ - **mod** `src/commands/server.ts` — instantiates `InitiativeTracker` and passes to `new AgentServer`.
19
+
20
+ ## Decision-point inventory
21
+
22
+ 1. **Persistence location**: `.instar/initiatives.json` at the root of stateDir. Follows the same convention as `jobs.json`, `telegram-messages.jsonl`, etc. Alternative considered: nest under `.instar/state/initiatives/`. Rejected because the existing convention for top-level agent state is flat JSON at stateDir root.
23
+ 2. **Persistence format**: single JSON file with `{ initiatives: [...] }` root (same shape as AttentionItem persistence). Not a directory-per-initiative. Rationale: expected volume is ≤50 records, fits comfortably in one atomic write; per-record writes would risk torn state.
24
+ 3. **Ready-to-advance signal definition**: emits when the previous phase is `done` AND the current phase is `pending` (untouched). Does not emit once the current phase is `in-progress` — by then the advance has started and the signal would become noise. (Bug discovered mid-test-run and corrected.)
25
+ 4. **Staleness threshold**: 7 days (`STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000`). Only applies to `active` initiatives. Configurable in a future change if noise is observed.
26
+ 5. **Id validation**: lowercase kebab-case, 1–63 chars, must start with alphanumeric. Matches slug conventions used elsewhere (job slugs, topic names).
27
+
28
+ ## 1. Over-block review
29
+
30
+ No blocking behavior introduced. Every endpoint is opt-in (503 when `initiativeTracker` is null, which cannot happen in practice since the server always instantiates it). No existing routes or middleware are changed. No pre-commit hook, git-sync behavior, or session lifecycle is touched.
31
+
32
+ ## 2. Under-block review
33
+
34
+ N/A — there is no block-or-pass decision. The tracker is purely a persistence + query surface.
35
+
36
+ ## 3. Level-of-abstraction fit
37
+
38
+ - Persistence logic lives in `InitiativeTracker` class (domain layer).
39
+ - HTTP shape lives in `routes.ts` (transport layer).
40
+ - Construction lives in `commands/server.ts` (composition root).
41
+ - Tests hit both the class layer and the HTTP layer independently.
42
+
43
+ This matches the pattern established by `BackupManager`, `RelationshipManager`, `FeedbackManager`.
44
+
45
+ ## 4. Signal-vs-authority review
46
+
47
+ The digest scan emits **signals only** (`DigestItem[]`). It has zero blocking authority — callers (daily digest job, dashboard UI, the user) decide what to do with the signal. The daily digest job (separate commit) will use these signals to decide whether to push a Telegram notification; the threshold for pushing is the job's concern, not the tracker's.
48
+
49
+ ## 5. Interactions review
50
+
51
+ - **Does not interact with Telegram**: tracker has no knowledge of topics, messages, or channels. The separate daily-digest job will bridge signal → Telegram.
52
+ - **Does not interact with JobScheduler**: the digest job reads the tracker but lives separately.
53
+ - **Does not interact with BackupManager**: `.instar/initiatives.json` is picked up by default backup globbing (no explicit include needed).
54
+ - **Does not conflict with AttentionItem**: complementary — AttentionItem is for single-shot actionables, Initiative is for multi-phase long-lived work. A future convenience could auto-create an AttentionItem from a `needs-user` digest row, but that's a separate design decision.
55
+
56
+ ## 6. External surfaces
57
+
58
+ - **HTTP API**: 7 new routes under `/initiatives`, all behind the existing auth middleware. No public unauthenticated surface.
59
+ - **File system**: one new file (`.instar/initiatives.json`), atomic-rename writes prevent torn state.
60
+ - **Dashboard**: not wired yet — separate commit adds the Initiatives tab.
61
+
62
+ ## 7. Rollback cost
63
+
64
+ Low. `git revert` removes the tracker class, routes, wiring, and tests. `.instar/initiatives.json` persists on disk but is ignored by all revived code paths (no other consumer reads it). The user can delete the file manually if desired, or leave it as dead data.
65
+
66
+ ## Conclusion
67
+
68
+ Additive-only change. No existing behavior modified. 43 new tests (28 class + 15 route), all passing. `tsc --noEmit` clean. Clear to ship as commit 1 of 4 for the Initiative Tracker feature.
69
+
70
+ ## Evidence pointers
71
+
72
+ - Unit tests: `tests/unit/InitiativeTracker.test.ts` (28 pass).
73
+ - Integration tests: `tests/unit/routes-initiatives.test.ts` (15 pass).
74
+ - Prior Phase A tests still green (spot-checked: `routes-prGatePhaseGate`, `PostUpdateMigrator-gitignore`, `PostUpdateMigrator-prPipelineArtifacts` — 37 pass).
75
+ - `npx tsc --noEmit` — clean.
@@ -0,0 +1,94 @@
1
+ # Side-Effects Review — Initiative Tracker dashboard tab
2
+
3
+ **Version / slug:** `initiative-tracker-dashboard-tab`
4
+ **Date:** `2026-04-17`
5
+ **Author:** `Echo (instar-developing agent)`
6
+ **Second-pass reviewer:** `not required — read-only UI surface, identical XSS-safety pattern to the PR Pipeline tab shipped in Phase A commit fb16f5c`
7
+
8
+ ## Summary of the change
9
+
10
+ Adds an "Initiatives" tab to the dashboard that renders the initiatives
11
+ board at a glance: each initiative's title, status, phases (as pills),
12
+ last-touched date, blockers, and a digest summary of actionable signals
13
+ (stale / needs-user / next-check-due / ready-to-advance).
14
+
15
+ Read-only. No mutation buttons in this commit — the tracker already
16
+ exposes CRUD via the HTTP API, but the dashboard does not wire any of
17
+ those mutating endpoints to UI actions at this layer. Keeps the XSS
18
+ attack surface minimal while the tracker is new.
19
+
20
+ Files modified:
21
+ - `dashboard/index.html` — tab button, panel container, TAB_REGISTRY
22
+ entry, `loadInitiatives()` function (~170 lines).
23
+ - **new** `tests/unit/dashboard-initiativesTab.test.ts` — 9 smoke tests
24
+ inspecting the HTML to verify wiring, XSS invariant, and signal
25
+ rendering.
26
+
27
+ ## Decision-point inventory
28
+
29
+ 1. **Read-only vs. mutable UI**: read-only. The API supports mutation
30
+ via CRUD + phase-transition, but exposing those as buttons would
31
+ widen the XSS surface and introduce state-mutation from the browser.
32
+ Defer to a later commit after live-use feedback tells us what, if
33
+ anything, deserves a UI button.
34
+ 2. **Digest panel placement**: top of tab content, only visible when
35
+ `digestRes.items.length > 0`. Zero items = quiet panel. Matches the
36
+ "don't push noise" principle for the digest job.
37
+ 3. **Status filter default**: 'active'. Most-useful-first; user can
38
+ flip to 'All' / 'Completed' / 'Archived' / 'Abandoned' via the
39
+ dropdown.
40
+ 4. **Count badge**: tab button shows the count of initiatives matching
41
+ the current filter (matches Sessions / Jobs tab convention).
42
+
43
+ ## 1. Over-block / 2. Under-block review
44
+
45
+ N/A — tab is read-only. No gating decision in this surface.
46
+
47
+ ## 3. Level-of-abstraction fit
48
+
49
+ UI rendering lives next to the PR Pipeline tab (same file, same
50
+ patterns, same XSS invariants). `loadInitiatives()` is a sibling of
51
+ `loadPrPipeline()` in layout and structure.
52
+
53
+ ## 4. Signal-vs-authority review
54
+
55
+ The digest panel renders **signals** produced by the tracker; the UI
56
+ has no blocking authority over anything. A human user is a smart gate;
57
+ showing them signals is exactly the right hand-off.
58
+
59
+ ## 5. Interactions review
60
+
61
+ - **No effect on PR Pipeline tab**: separate panel, separate loader,
62
+ separate TAB_REGISTRY entry.
63
+ - **No effect on other tabs**: tab registry is additive.
64
+ - **XSS**: all content goes through `textContent` or DOM element
65
+ construction; `innerHTML` is not used anywhere inside
66
+ `loadInitiatives()`. Enforced by a test.
67
+
68
+ ## 6. External surfaces
69
+
70
+ - Dashboard HTML served to authenticated users (dashboard PIN or auth
71
+ token). No new public surface.
72
+ - Two new GET requests when the tab activates:
73
+ `GET /initiatives?status=active`, `GET /initiatives/digest`. Both
74
+ behind existing auth middleware.
75
+
76
+ ## 7. Rollback cost
77
+
78
+ Very low. `git revert` removes the tab button, panel, registry entry,
79
+ and loader function — no residual state.
80
+
81
+ ## Conclusion
82
+
83
+ Additive UI surface. 9 new smoke tests (all passing). `tsc --noEmit`
84
+ not affected (HTML/JS, not TS). XSS invariant guarded by a test that
85
+ greps the function body for `.innerHTML =` assignments.
86
+
87
+ Commit 2 of 4 for the Initiative Tracker feature (commit 1 = core +
88
+ API; commit 3 = daily digest job; commit 4 = seed real initiatives).
89
+
90
+ ## Evidence pointers
91
+
92
+ - Smoke tests: `tests/unit/dashboard-initiativesTab.test.ts` (9 pass).
93
+ - Referenced spec: `docs/specs/INITIATIVE-TRACKER-SPEC.md`.
94
+ - Prior art: `dashboard/index.html` commit `fb16f5c` (PR Pipeline tab).