+ Audit report for the Islandflow terminal view, formatted for handoff and review. This preserves the
+ full findings set: scorecard, anti-pattern verdict, executive summary, detailed issues, systemic
+ patterns, positive findings, and recommended follow-up commands.
+
+ The terminal does not read as generic AI-generated UI overall. It has a coherent
+ instrument-panel identity, consistent density, and restrained accent use. The biggest problems are
+ implementation quality issues: invalid nested interactive controls, inaccessible drawer behavior,
+ weak focus treatment, mobile layouts that depend on horizontal scrolling, token drift, and repeated
+ banned side-stripe accents.
+
+
+
+
+
Audit Health Score
+
+
+
+
#
+
Dimension
+
Score
+
Key Finding
+
+
+
+
+
1
+
Accessibility
+
2/4
+
Invalid nested interactive controls in options rows.
+
+
+
2
+
Performance
+
3/4
+
Virtualization is good, but blur-heavy chrome and overlays add avoidable cost.
+
+
+
3
+
Responsive Design
+
2/4
+
Core tables rely on large fixed minimum widths and horizontal scrolling.
+
+
+
4
+
Theming
+
2/4
+
Token base exists, but many hard-coded colors and undefined vars bypass it.
+
+
+
5
+
Anti-Patterns
+
2/4
+
Repeated side-stripe accents violate the stated design bans.
+
+
+
Total
+
+
11/20
+
Acceptable
+
+
+
+
+
+
+
Anti-Patterns Verdict
+
+ Pass, with caveats.
+
+
+ The terminal does not look AI-generated overall. It has a coherent instrument-panel identity,
+ consistent density, and restrained accent use. The main tells are implementation-level:
+ banned side-stripe accents on live rows, decorative blur-heavy chrome, and some product-UI
+ typography choices that drift toward display styling.
+
+
+
+
+
Executive Summary
+
+
Audit Health Score: 11/20 (Acceptable)
+
Total issues found: 9
+
Severity mix: P0: 0, P1: 5, P2: 3, P3: 1
+
+ Top issues: nested buttons inside clickable options rows, drawers that are not true
+ accessible dialogs, suppressed focus indicators, mobile dependence on oversized horizontal tables,
+ and repeated banned side-stripe row styling.
+
Impact: Decorated option rows render as outer <button> elements containing inner contract-focus <button> elements. This is invalid HTML and can create inconsistent tab order, click handling, and screen-reader output.
Recommendation: Split row selection and contract focus into non-nested controls. Use a non-button row container with one explicit action button, or keep the row as the only button and turn inner controls into non-interactive text.
[P1] Drawer Panels Are Visually Drawers, Not Accessible Dialogs
+
Location:apps/web/app/terminal.tsx:4524-4629, 4639-4737, 4747-4841, 4850-4952, close handling at 5070-5102
+
Category: Accessibility
+
Impact: The drawers close on outside click and Escape, but they lack role="dialog", aria-modal, focus entry, focus return, and trap behavior. Keyboard users can tab behind the drawer and lose context.
Impact: Several controls explicitly remove the browser outline. Some surfaces get only a subtle background shift, which is weaker than a reliable visible focus ring, especially in dense data views.
+
WCAG/Standard: WCAG 2.4.7 Focus Visible
+
Recommendation: Restore strong :focus-visible treatment on inputs, row buttons, and inline instrument actions using a consistent high-contrast ring or border treatment.
Impact: The app uses role="table" and role="row" but not full table semantics such as rowgroup, columnheader, and cell roles. Screen readers will get a weaker structural model than a real table or fully formed ARIA grid.
+
WCAG/Standard: WCAG 1.3.1 Info and Relationships
+
Recommendation: Prefer semantic <table> markup where possible, or complete the ARIA table structure consistently.
Impact: Major views keep min-width values like 1280px, 1260px, 900px, and 820px. The mobile fallback is horizontal scroll rather than structural adaptation, which increases cognitive load and makes comparison harder on phones and small tablets.
+
WCAG/Standard: Responsive design best practice
+
Recommendation: Define compact column sets, progressive disclosure, or cardless stacked row summaries under mobile breakpoints instead of preserving full desktop schema.
+
Suggested command:$impeccable adapt terminal view
+
+
+
+
[P2] Touch Targets Are Below Recommended Minimum In Key Controls
Impact: Controls and chips commonly bottom out around 32px height. That is workable on desktop, but it is tight for touch use and increases mis-taps on mobile.
+
WCAG/Standard: WCAG 2.5.5 Target Size (AAA), platform mobile guidance
+
Recommendation: Raise interactive height to at least 40px, ideally 44px, for topbar controls, focus chips, and filter triggers under touch breakpoints.
Impact: The file starts with a clear token layer, but many later rules bypass it with hard-coded hex values. That makes palette evolution and future theme work harder.
+
WCAG/Standard: Theming and system quality
+
Recommendation: Replace one-off literals with named variables, especially amber text variants, chart surface background, and severity-strip foreground colors.
Impact:var(--text-muted) and var(--muted) are referenced but not defined in :root. Those declarations will fail and fall back to inherited color, which makes the result fragile and inconsistent.
+
WCAG/Standard: CSS correctness
+
Recommendation: Replace them with existing tokens such as --text-dim or define the missing variables explicitly.
Impact:backdrop-filter: blur(12px) and blur(18px) on persistent UI surfaces add cost and push the product UI slightly toward decorative glass treatment, which the design rules explicitly warn against as a default.
+
WCAG/Standard: Performance and product-design guidance
+
Recommendation: Keep blur only where separation is essential, or replace it with tonal contrast and border treatment.
Impact: The interface repeatedly uses border-left: 3px to communicate severity, direction, and classifier state. That is one of the skill's explicit banned patterns and makes rows feel more template-like than intentional.
+
WCAG/Standard: Design-system rule
+
Recommendation: Move semantic emphasis into full-row tinting, chips, iconography, or stronger text hierarchy instead of colored side rails.
Accessibility semantics are strongest at the surface level, labels and buttons exist, but weaker in composite patterns, drawers, virtualized tables, and focus handling.
+
The responsive strategy is mostly preserve desktop density and allow scrolling, not restructure the workflow for narrow screens.
+
The CSS starts from a tokenized system, then drifts into literal color values in later component rules.
+
The visual system is disciplined overall, but a few repeated product bans, side stripes and default blur, show up across multiple components.
+
+
+
+
+
Positive Findings
+
+
The terminal has a clear, distinctive product identity without falling into meme-trader styling.
+
Virtualized list rendering is the right performance baseline for these dense live data views.
+
The top-level shell and pane structure are predictable and support fast scanning.
+
Core inputs are labelled, and many actionable rows are implemented as real buttons instead of click-only divs.
+
Color usage is generally restrained and semantically meaningful, even where implementation cleanup is still needed.
+ Turn document for the terminal shell hardening pass in apps/web/app/terminal.tsx,
+ apps/web/app/globals.css, and apps/web/app/terminal.test.ts.
+
+
+ The work focused on production resilience in the main terminal shell: keyboard access, focus visibility,
+ long-text behavior, topbar wrapping, and ticker filter normalization for pasted or malformed input.
+
+ The terminal shell now behaves more predictably under constrained widths and less-perfect input. The
+ changes stay small and local, but improve accessibility and reduce UI breakage risk in the top-level
+ workflow.
+
+
+
+
+
Changes Made
+
+
Added a skip link targeting the main terminal content region.
+
Added primary navigation semantics with aria-label and aria-current.
+
Added visible keyboard focus treatment for nav links, shell buttons, and the instrument chip action.
+
Allowed topbar action groups to wrap instead of forcing overflow at narrower widths.
+
Added long-text hardening for the brand name and selected instrument chip.
Added unit tests for ticker filter normalization and parsing.
+
+
+
+
+
Context
+
+ Islandflow's terminal is an evidence-first product surface used under time pressure. The shell is not a
+ decorative wrapper. It controls navigation, global filter state, and mode switching, so failures here can
+ degrade every route at once.
+
+
+ The hardening pass stayed focused on shell-level reliability rather than introducing broader layout or
+ component refactors.
+
+
+
+
+
Important Implementation Details
+
+
+
+ The ticker filter path now normalizes paste-heavy input before it reaches state-dependent parsing.
+ This covers full-width commas, repeated whitespace, control characters, and casing drift.
+
+ Shell semantics were strengthened without changing the route structure. The new skip link and current-page
+ annotation improve keyboard and assistive navigation while staying visually quiet during normal use.
+
Regression coverage was added for normalization and token parsing of ticker input.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
+ Dolt sync limitation:bd dolt pull failed earlier in the session because no Dolt
+ remote is configured in this workspace. The code work continued, but beads remote sync was not available.
+
+
+ Scope control: this pass hardened the shell only. It did not audit every downstream pane,
+ drawer, or popover for similar edge cases.
+
+
+ Workflow status: this document records the implementation and validation, but the work was not
+ committed or pushed as part of this turn.
+
+
+
+
+
+
Follow-up Work
+
+
No follow-up issue was created from this hardening pass beyond the completed beads item islandflow-6ri.
+
If terminal adaptation work continues, the next pass should examine small-screen drawer behavior and popover placement under dense live states.
+
+
+ Document created to satisfy the required turn-documentation step for implementation changes.
+
+ The terminal view now adapts more deliberately across desktop, tablet, and phone contexts. The update preserves the evidence-console density on larger screens while making controls, navigation, filters, drawers, and data lanes more usable on smaller touch devices.
+
+
+
+
+
Changes Made
+
+
Changed the tablet rail from a stacked desktop sidebar into a sticky horizontal command rail with scrollable navigation and metric strips.
+
Added phone-specific touch sizing for buttons, navigation links, filter controls, and pane actions.
+
Converted the flow filter panel and evidence drawers into bottom-sheet style surfaces on small screens.
+
Adjusted pane spacing, page heading scale, and table row heights to fit small screens without hiding core workflow content.
+
Kept dense data tables horizontally scrollable instead of crushing columns into unreadable cells.
+
Replaced colored side-stripe table accents with full-row inset outlines to preserve semantic color without banned side-stripe treatment.
+
+
+
+
+
Context
+
+ Islandflow is a product-register interface for serious traders and researchers. The relevant scene is a user moving between a desktop workstation and a smaller companion screen while monitoring live or replayed market evidence. The design keeps the dark, composed terminal atmosphere because it supports high-contrast scanning in dim, focused trading conditions.
+
+
+
+
+
Important Implementation Details
+
+
The responsive behavior remains CSS-only in globals.css, avoiding component churn in the large terminal module.
+
The 1180px breakpoint now rethinks navigation as a horizontal rail rather than a full-width vertical block.
+
The 720px breakpoint shifts to touch-first behavior with 44px minimum targets and bottom-reachable overlays.
+
The data tables retain their information architecture by using horizontal scroll lanes and slightly larger mobile rows.
+
+
+
+
+
Validation
+
+
Ran bun test apps/web/app/terminal.test.ts apps/web/app/routes.test.ts: 64 passing tests.
+
Ran bun --cwd=apps/web run build: production Next.js build completed successfully.
+
Searched for banned colored side-stripe CSS patterns in the app stylesheet after the update: none found.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
No real-device browser session was available in this turn, so tactile behavior was validated through CSS structure and production build checks rather than physical-device testing.
+
The tables intentionally remain horizontally scrollable on phones because hiding key evidence columns would damage the investigative workflow.
+
Bottom-sheet drawers can still cover part of the active tape on very short screens, mitigated by max-height limits and internal scrolling.
+
+
+
+
+
Follow-up Work
+
No new follow-up issue was filed. The current adaptation is self-contained under islandflow-1f5.
+
+
+
+
From 4b2c8de161129d62555e20a57062c1f0042d9b1f Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Thu, 14 May 2026 18:34:09 -0400
Subject: [PATCH 005/107] Document reconciled PR conflicts
---
.beads/issues.jsonl | 2 +
...026-05-14-1833-reconcile-pr-conflicts.html | 170 ++++++++++++++++++
2 files changed, 172 insertions(+)
create mode 100644 docs/turns/2026-05-14-1833-reconcile-pr-conflicts.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 51bb12b..0caee72 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -8,6 +8,8 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-t8s","title":"Reconcile merge conflicts on impeccable","description":"Resolve the PR branch conflicts against main while preserving terminal hardening, responsive adaptation, and related test coverage.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:32:40Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:34:03Z","started_at":"2026-05-14T22:33:05Z","closed_at":"2026-05-14T22:34:03Z","close_reason":"Rebased impeccable onto main, resolved the terminal test conflict, and revalidated the web app.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-7ch","title":"Reconcile merge conflicts on impeccable","description":"Resolve the current merge or rebase conflicts on the impeccable branch and preserve the intended terminal UI and documentation changes.","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:30:10Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:30:29Z","started_at":"2026-05-14T22:30:29Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-1f5","title":"Adapt terminal view for responsive use","description":"Improve the terminal view so it remains usable across desktop, tablet, and small-screen contexts without hiding core workflow functionality.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:22:18Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:25:22Z","started_at":"2026-05-14T22:22:25Z","closed_at":"2026-05-14T22:25:22Z","close_reason":"Terminal view adapted for responsive and touch-first contexts; tests and web build passed.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-uhi","title":"Publish terminal turn document to GitHub Pages","description":"Why: the completed turn document should be reachable on the user's GitHub Pages site. What: determine the GitHub Pages publishing path for dirtydishes.github.io, place the terminal hardening turn document at a stable HTML URL, validate the file location, and update beads status for the publishing work.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:15:23Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:17:39Z","started_at":"2026-05-14T22:15:34Z","closed_at":"2026-05-14T22:17:39Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-6ri","title":"Harden terminal shell view","description":"Why: the terminal shell needs production hardening for focus visibility, long labels, and ticker entry edge cases so the main workflow remains stable under constrained widths and imperfect input. What: tighten shell semantics and input handling, prevent overflow in the top bar and rail, and add regression tests for the ticker filter normalization path.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T08:56:45Z","created_by":"dirtydishes","updated_at":"2026-05-14T08:58:46Z","started_at":"2026-05-14T08:56:53Z","closed_at":"2026-05-14T08:58:46Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/docs/turns/2026-05-14-1833-reconcile-pr-conflicts.html b/docs/turns/2026-05-14-1833-reconcile-pr-conflicts.html
new file mode 100644
index 0000000..1269cc8
--- /dev/null
+++ b/docs/turns/2026-05-14-1833-reconcile-pr-conflicts.html
@@ -0,0 +1,170 @@
+
+
+
+
+
+ Turn Summary: Reconcile PR Conflicts
+
+
+
+
+
+
2026-05-14 18:33
+
Reconcile PR Conflicts
+
Branch: impeccable · Beads issue: islandflow-t8s
+
+
+
+
Summary
+
+ Rebasing impeccable onto the latest main exposed a conflict in apps/web/app/terminal.test.ts. The branch now preserves both the terminal hardening coverage from this PR and the ticker parsing coverage already present on main.
+
+
+
+
+
Changes Made
+
+
Rebased the PR branch onto origin/main.
+
Resolved the only manual conflict in apps/web/app/terminal.test.ts.
+
Kept both the synthetic admin visibility test and the ticker parsing helper tests.
+
Preserved the rest of the branch changes while letting Git auto-merge the terminal stylesheet and app shell updates.
+
+
+
+
+
Context
+
+ The PR already contained terminal UI hardening and responsive adaptation work. The base branch moved underneath it with additional terminal test coverage, so the conflict needed to be reconciled without dropping either set of expectations.
+
+
+
+
+
Important Implementation Details
+
+
The conflict was limited to test imports and test blocks, not runtime terminal logic.
+
The merged test file now imports both isSyntheticAdminVisible and parseTickerFilterInput.
+
An explicit undefined case was kept for the admin visibility helper to preserve the stricter branch-side regression coverage.
+
+
+
+
+
Validation
+
+
Ran bun test apps/web/app/terminal.test.ts apps/web/app/routes.test.ts: 65 passing tests.
+
Ran bun --cwd=apps/web run build: production Next.js build completed successfully on the rebased branch.
+
Verified there were no remaining conflict markers in tracked project files before continuing the rebase.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
The conflict resolution was test-only, so no additional UI screenshots or browser checks were needed for this turn.
+
The branch history changed because the fix was done via rebase, which requires pushing the updated branch tip back to the PR.
+
+
+
+
+
Follow-up Work
+
No further follow-up was identified from this conflict resolution. The branch is ready for PR mergeability to be re-evaluated after push.
+
+
+
+
From 66c486deb92d16f229094ae22cbaa319c634ab33 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 15 May 2026 00:57:10 -0400
Subject: [PATCH 006/107] Add Pi plan mode command
---
.beads/issues.jsonl | 1 +
.pi/extensions/plan-mode.ts | 82 +++++++++++++++++++++
docs/turns/2026-05-15-add-pi-plan-mode.html | 55 ++++++++++++++
3 files changed, 138 insertions(+)
create mode 100644 .pi/extensions/plan-mode.ts
create mode 100644 docs/turns/2026-05-15-add-pi-plan-mode.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 0caee72..882b8ad 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -8,6 +8,7 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-hio","title":"Add Pi /plan command for plan mode","description":"Create a Pi extension so typing /plan activates plan mode instructions and guards against implementation file edits until disabled.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T04:56:00Z","created_by":"dirtydishes","updated_at":"2026-05-15T04:57:03Z","started_at":"2026-05-15T04:56:03Z","closed_at":"2026-05-15T04:57:03Z","close_reason":"Implemented project-local Pi /plan extension with plan-mode guardrails.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-t8s","title":"Reconcile merge conflicts on impeccable","description":"Resolve the PR branch conflicts against main while preserving terminal hardening, responsive adaptation, and related test coverage.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:32:40Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:34:03Z","started_at":"2026-05-14T22:33:05Z","closed_at":"2026-05-14T22:34:03Z","close_reason":"Rebased impeccable onto main, resolved the terminal test conflict, and revalidated the web app.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-7ch","title":"Reconcile merge conflicts on impeccable","description":"Resolve the current merge or rebase conflicts on the impeccable branch and preserve the intended terminal UI and documentation changes.","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:30:10Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:30:29Z","started_at":"2026-05-14T22:30:29Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-1f5","title":"Adapt terminal view for responsive use","description":"Improve the terminal view so it remains usable across desktop, tablet, and small-screen contexts without hiding core workflow functionality.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:22:18Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:25:22Z","started_at":"2026-05-14T22:22:25Z","closed_at":"2026-05-14T22:25:22Z","close_reason":"Terminal view adapted for responsive and touch-first contexts; tests and web build passed.","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/.pi/extensions/plan-mode.ts b/.pi/extensions/plan-mode.ts
new file mode 100644
index 0000000..d80ef01
--- /dev/null
+++ b/.pi/extensions/plan-mode.ts
@@ -0,0 +1,82 @@
+import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
+import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
+
+const PLAN_MODE_PROMPT = `PLAN MODE IS ACTIVE.
+
+You must not modify code, configuration, tests, documentation, project files, or external files. Do not use write or edit tools. Do not run shell commands that create, modify, delete, move, format, install, commit, push, or otherwise mutate files, dependencies, services, or repository state.
+
+You may inspect files and run read-only discovery commands. Produce a concise implementation plan, include risks and validation steps, then ask the user whether they want to proceed with implementation. If the user asks to save the plan, create only a plan document under docs/plans/ after explicitly confirming that saving the plan is allowed.`;
+
+let planMode = false;
+
+function looksMutatingShell(command: string): boolean {
+ const normalized = command.toLowerCase();
+ const mutatingPatterns = [
+ /(^|[;&|()\s])(>|>>|tee\b)/,
+ /(^|[;&|()\s])(rm|rmdir|mv|cp|mkdir|touch|chmod|chown|ln|truncate)\b/,
+ /(^|[;&|()\s])(git\s+(add|commit|push|pull|merge|rebase|reset|checkout|switch|restore|stash|clean|tag|branch)|bd\s+(create|update|close|reopen|dolt\s+push))\b/,
+ /(^|[;&|()\s])(bun|npm|pnpm|yarn|npx)\s+(install|add|remove|update|upgrade|dedupe|run\s+(build|dev|format|lint:fix))\b/,
+ /(^|[;&|()\s])(python|python3|node|ruby|perl)\b.*\b(-w|writefile|appendfile|unlink|rmdir|mkdir|rename)\b/,
+ /(^|[;&|()\s])(docker|docker-compose)\s+(run|compose\s+up|up|down|rm|rmi|build|push|pull)\b/,
+ ];
+
+ return mutatingPatterns.some((pattern) => pattern.test(normalized));
+}
+
+export default function planModeExtension(pi: ExtensionAPI) {
+ pi.registerCommand("plan", {
+ description: "Activate plan mode. Use '/plan off' to return to implementation mode.",
+ handler: async (args, ctx) => {
+ const command = args.trim().toLowerCase();
+
+ if (["off", "disable", "disabled", "false", "0"].includes(command)) {
+ planMode = false;
+ ctx.ui.setStatus("plan-mode", undefined);
+ ctx.ui.notify("Plan mode disabled. Implementation tools are available again.", "info");
+ return;
+ }
+
+ planMode = true;
+ ctx.ui.setStatus("plan-mode", "PLAN");
+ ctx.ui.notify("Plan mode enabled. File mutation tools and mutating shell commands are blocked.", "success");
+ },
+ });
+
+ pi.registerCommand("implement", {
+ description: "Disable plan mode and return to implementation mode.",
+ handler: async (_args, ctx) => {
+ planMode = false;
+ ctx.ui.setStatus("plan-mode", undefined);
+ ctx.ui.notify("Plan mode disabled. Implementation mode is active.", "info");
+ },
+ });
+
+ pi.on("session_start", async (_event, ctx) => {
+ if (planMode) ctx.ui.setStatus("plan-mode", "PLAN");
+ });
+
+ pi.on("before_agent_start", async (event) => {
+ if (!planMode) return;
+ return {
+ systemPrompt: `${event.systemPrompt}\n\n${PLAN_MODE_PROMPT}`,
+ };
+ });
+
+ pi.on("tool_call", async (event) => {
+ if (!planMode) return;
+
+ if (event.toolName === "write" || event.toolName === "edit") {
+ return {
+ block: true,
+ reason: "Plan mode is active. Use /plan off or /implement before modifying files.",
+ };
+ }
+
+ if (isToolCallEventType("bash", event) && looksMutatingShell(event.input.command ?? "")) {
+ return {
+ block: true,
+ reason: "Plan mode is active. Mutating shell commands are blocked. Use /plan off or /implement to proceed.",
+ };
+ }
+ });
+}
diff --git a/docs/turns/2026-05-15-add-pi-plan-mode.html b/docs/turns/2026-05-15-add-pi-plan-mode.html
new file mode 100644
index 0000000..87c93a2
--- /dev/null
+++ b/docs/turns/2026-05-15-add-pi-plan-mode.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+ Add Pi /plan Mode
+
+
+
+
Add Pi /plan Mode
+
+
Summary
+
Added a project-local Pi extension that lets users type /plan to activate a guarded planning mode, then /plan off or /implement to return to implementation mode.
+
+
+
Changes Made
+
+
Created .pi/extensions/plan-mode.ts.
+
Registered a /plan command that enables plan mode.
+
Registered a /implement command and /plan off argument to disable plan mode.
+
Added tool-call guards that block write, edit, and common mutating shell commands while plan mode is active.
+
Added a turn document for this change.
+
+
+
Context
+
Pi does not ship with built-in plan mode. Its documented extension system supports custom slash commands and tool-call interception, which fits this workflow without patching Pi internals.
When active, plan mode appends explicit system instructions before each agent turn and blocks file mutation tools. Bash commands are screened with conservative patterns for filesystem, git, package-manager, and Docker mutations.
+
+
Validation
+
+
Ran NODE_PATH=/opt/homebrew/lib/node_modules bun --check .pi/extensions/plan-mode.ts successfully.
+
Initial bun --check .pi/extensions/plan-mode.ts failed because the Pi package is installed globally, not as a repo dependency. Retried with NODE_PATH pointed at Homebrew global Node modules.
+
+
+
Issues, Limitations, and Mitigations
+
+
The bash mutation detector is intentionally conservative but cannot perfectly classify every shell command. Direct Pi write and edit calls are fully blocked.
+
The extension is project-local, so it activates automatically for Pi sessions launched in this repository. To use it everywhere, copy it to ~/.pi/agent/extensions/.
+
+
+
Follow-up Work
+
No required follow-up work. Beads issue: islandflow-hio.
+
+
From e19272d39a165d7c17193519feea5e0a41ac2c90 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 15 May 2026 00:57:53 -0400
Subject: [PATCH 007/107] Revert "Add Pi plan mode command"
This reverts commit 66c486deb92d16f229094ae22cbaa319c634ab33.
---
.beads/issues.jsonl | 1 -
.pi/extensions/plan-mode.ts | 82 ---------------------
docs/turns/2026-05-15-add-pi-plan-mode.html | 55 --------------
3 files changed, 138 deletions(-)
delete mode 100644 .pi/extensions/plan-mode.ts
delete mode 100644 docs/turns/2026-05-15-add-pi-plan-mode.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 882b8ad..0caee72 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -8,7 +8,6 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-hio","title":"Add Pi /plan command for plan mode","description":"Create a Pi extension so typing /plan activates plan mode instructions and guards against implementation file edits until disabled.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T04:56:00Z","created_by":"dirtydishes","updated_at":"2026-05-15T04:57:03Z","started_at":"2026-05-15T04:56:03Z","closed_at":"2026-05-15T04:57:03Z","close_reason":"Implemented project-local Pi /plan extension with plan-mode guardrails.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-t8s","title":"Reconcile merge conflicts on impeccable","description":"Resolve the PR branch conflicts against main while preserving terminal hardening, responsive adaptation, and related test coverage.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:32:40Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:34:03Z","started_at":"2026-05-14T22:33:05Z","closed_at":"2026-05-14T22:34:03Z","close_reason":"Rebased impeccable onto main, resolved the terminal test conflict, and revalidated the web app.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-7ch","title":"Reconcile merge conflicts on impeccable","description":"Resolve the current merge or rebase conflicts on the impeccable branch and preserve the intended terminal UI and documentation changes.","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:30:10Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:30:29Z","started_at":"2026-05-14T22:30:29Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-1f5","title":"Adapt terminal view for responsive use","description":"Improve the terminal view so it remains usable across desktop, tablet, and small-screen contexts without hiding core workflow functionality.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:22:18Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:25:22Z","started_at":"2026-05-14T22:22:25Z","closed_at":"2026-05-14T22:25:22Z","close_reason":"Terminal view adapted for responsive and touch-first contexts; tests and web build passed.","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/.pi/extensions/plan-mode.ts b/.pi/extensions/plan-mode.ts
deleted file mode 100644
index d80ef01..0000000
--- a/.pi/extensions/plan-mode.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
-import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
-
-const PLAN_MODE_PROMPT = `PLAN MODE IS ACTIVE.
-
-You must not modify code, configuration, tests, documentation, project files, or external files. Do not use write or edit tools. Do not run shell commands that create, modify, delete, move, format, install, commit, push, or otherwise mutate files, dependencies, services, or repository state.
-
-You may inspect files and run read-only discovery commands. Produce a concise implementation plan, include risks and validation steps, then ask the user whether they want to proceed with implementation. If the user asks to save the plan, create only a plan document under docs/plans/ after explicitly confirming that saving the plan is allowed.`;
-
-let planMode = false;
-
-function looksMutatingShell(command: string): boolean {
- const normalized = command.toLowerCase();
- const mutatingPatterns = [
- /(^|[;&|()\s])(>|>>|tee\b)/,
- /(^|[;&|()\s])(rm|rmdir|mv|cp|mkdir|touch|chmod|chown|ln|truncate)\b/,
- /(^|[;&|()\s])(git\s+(add|commit|push|pull|merge|rebase|reset|checkout|switch|restore|stash|clean|tag|branch)|bd\s+(create|update|close|reopen|dolt\s+push))\b/,
- /(^|[;&|()\s])(bun|npm|pnpm|yarn|npx)\s+(install|add|remove|update|upgrade|dedupe|run\s+(build|dev|format|lint:fix))\b/,
- /(^|[;&|()\s])(python|python3|node|ruby|perl)\b.*\b(-w|writefile|appendfile|unlink|rmdir|mkdir|rename)\b/,
- /(^|[;&|()\s])(docker|docker-compose)\s+(run|compose\s+up|up|down|rm|rmi|build|push|pull)\b/,
- ];
-
- return mutatingPatterns.some((pattern) => pattern.test(normalized));
-}
-
-export default function planModeExtension(pi: ExtensionAPI) {
- pi.registerCommand("plan", {
- description: "Activate plan mode. Use '/plan off' to return to implementation mode.",
- handler: async (args, ctx) => {
- const command = args.trim().toLowerCase();
-
- if (["off", "disable", "disabled", "false", "0"].includes(command)) {
- planMode = false;
- ctx.ui.setStatus("plan-mode", undefined);
- ctx.ui.notify("Plan mode disabled. Implementation tools are available again.", "info");
- return;
- }
-
- planMode = true;
- ctx.ui.setStatus("plan-mode", "PLAN");
- ctx.ui.notify("Plan mode enabled. File mutation tools and mutating shell commands are blocked.", "success");
- },
- });
-
- pi.registerCommand("implement", {
- description: "Disable plan mode and return to implementation mode.",
- handler: async (_args, ctx) => {
- planMode = false;
- ctx.ui.setStatus("plan-mode", undefined);
- ctx.ui.notify("Plan mode disabled. Implementation mode is active.", "info");
- },
- });
-
- pi.on("session_start", async (_event, ctx) => {
- if (planMode) ctx.ui.setStatus("plan-mode", "PLAN");
- });
-
- pi.on("before_agent_start", async (event) => {
- if (!planMode) return;
- return {
- systemPrompt: `${event.systemPrompt}\n\n${PLAN_MODE_PROMPT}`,
- };
- });
-
- pi.on("tool_call", async (event) => {
- if (!planMode) return;
-
- if (event.toolName === "write" || event.toolName === "edit") {
- return {
- block: true,
- reason: "Plan mode is active. Use /plan off or /implement before modifying files.",
- };
- }
-
- if (isToolCallEventType("bash", event) && looksMutatingShell(event.input.command ?? "")) {
- return {
- block: true,
- reason: "Plan mode is active. Mutating shell commands are blocked. Use /plan off or /implement to proceed.",
- };
- }
- });
-}
diff --git a/docs/turns/2026-05-15-add-pi-plan-mode.html b/docs/turns/2026-05-15-add-pi-plan-mode.html
deleted file mode 100644
index 87c93a2..0000000
--- a/docs/turns/2026-05-15-add-pi-plan-mode.html
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
- Add Pi /plan Mode
-
-
-
-
Add Pi /plan Mode
-
-
Summary
-
Added a project-local Pi extension that lets users type /plan to activate a guarded planning mode, then /plan off or /implement to return to implementation mode.
-
-
-
Changes Made
-
-
Created .pi/extensions/plan-mode.ts.
-
Registered a /plan command that enables plan mode.
-
Registered a /implement command and /plan off argument to disable plan mode.
-
Added tool-call guards that block write, edit, and common mutating shell commands while plan mode is active.
-
Added a turn document for this change.
-
-
-
Context
-
Pi does not ship with built-in plan mode. Its documented extension system supports custom slash commands and tool-call interception, which fits this workflow without patching Pi internals.
When active, plan mode appends explicit system instructions before each agent turn and blocks file mutation tools. Bash commands are screened with conservative patterns for filesystem, git, package-manager, and Docker mutations.
-
-
Validation
-
-
Ran NODE_PATH=/opt/homebrew/lib/node_modules bun --check .pi/extensions/plan-mode.ts successfully.
-
Initial bun --check .pi/extensions/plan-mode.ts failed because the Pi package is installed globally, not as a repo dependency. Retried with NODE_PATH pointed at Homebrew global Node modules.
-
-
-
Issues, Limitations, and Mitigations
-
-
The bash mutation detector is intentionally conservative but cannot perfectly classify every shell command. Direct Pi write and edit calls are fully blocked.
-
The extension is project-local, so it activates automatically for Pi sessions launched in this repository. To use it everywhere, copy it to ~/.pi/agent/extensions/.
-
-
-
Follow-up Work
-
No required follow-up work. Beads issue: islandflow-hio.
Summary: Reduced chrome intensity across the Islandflow terminal by flattening backgrounds, softening amber usage, calming pane and overlay styling, and reducing motion emphasis so live data carries more of the visual weight.
+
+
+
Summary
+
The terminal now reads as a calmer product surface. The shell keeps its dark evidence-console identity, but the background texture, active-state glow, and overlay treatments no longer compete with the tape.
+
+
+
+
Changes Made
+
+
Moved core surface tokens in apps/web/app/globals.css to a quieter OKLCH palette.
+
Removed the visible shell grid texture and reduced ambient chrome contrast.
+
Flattened the rail, top bar, panes, lists, tables, drawers, filter popover, and synthetic control drawer.
+
Reduced amber wash on active buttons, filters, chips, and selected states.
+
Lowered the visual intensity of classified rows and semantic row outlines without removing meaning.
+
Switched secondary panel titles and control headings to calmer sans-serif treatment.
+
Added a reduced-motion rule to stop the connecting pulse when the user prefers reduced motion.
+
+
+
+
+
Context
+
Product context and design context were loaded from PRODUCT.md and DESIGN.md. This is a product-register surface, so the goal was not to make the terminal decorative in a different way. The goal was to let the tool disappear further into the task.
+
Scene sentence used to anchor the theme choice: a trader is scanning live tape on a large monitor in a dim room before the open, trying to stay focused on evidence instead of chrome.
+
+
+
+
Important Implementation Details
+
The main refinement was structural, not cosmetic. Instead of adding a new style layer, the change removes or softens existing intensity sources.
Classifier and severity rows still carry semantic feedback, but with reduced fill and border intensity so they highlight evidence instead of reading like alerts by default.
+
+
+
+
Validation
+
+
bun test apps/web/app/terminal.test.ts
+
bun --cwd=apps/web run build
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
This pass is CSS-only, so it does not change layout structure or information density.
+
Some semantic chips still use stronger color than the surrounding chrome. That is intentional so status remains scannable.
+
No screenshot-based review was captured in this turn, so final visual tuning may still benefit from a quick browser pass.
+
+
+
+
+
Follow-up Work
+
+
No follow-up issue created in this turn beyond the main work item.
+
If further quieting is wanted, the next pass should evaluate typography density inside tables and rail metrics rather than further reducing contrast globally.
+ Docker build failures were caused by an out-of-sync deployment workspace snapshot at
+ deployment/docker/workspace-root/. I refreshed the snapshot files so Docker builds
+ use current manifest and lock data for bun install --frozen-lockfile.
+
+
+
Changes Made
+
+
Created and claimed Beads issue islandflow-xll.
+
Ran bun run sync:docker-workspace.
+
Updated:
+
+
deployment/docker/workspace-root/package.json
+
deployment/docker/workspace-root/bun.lock
+
+
+
Added this turn report in docs/turns/.
+
+
+
Context
+
+ The deployment compose stack uses additional build context named workspace, pointing to
+ deployment/docker/workspace-root. Dockerfiles copy root files from that snapshot instead of directly
+ from repository root. If snapshot files are stale, frozen lockfile installs fail in container builds.
+
+
+
Important Implementation Details
+
+
Pre-sync check failed with snapshot drift and missing workspace entries in deployment lock data.
+
Post-sync check passed and reported the deployment snapshot is in sync.
+
This directly addresses the server-side error where frozen lockfile install detected changes.
+
+
+
Validation
+
+
Passed: bun run check:docker-workspace (after sync).
+
Passed: bun install --frozen-lockfile at repo root.
+
Could not run: docker compose -f deployment/docker/docker-compose.yml build candles web
+ because local Docker daemon was unavailable.
+
+
Cannot connect to the Docker daemon at unix:///Users/kell/.orbstack/run/docker.sock. Is the docker daemon running?
+
+
Issues, Limitations, and Mitigations
+
+
Limitation: Full container build validation was not possible in this environment.
+
Mitigation: Updated the exact snapshot files Docker consumes and verified snapshot consistency with the project check script.
+
+
+
Follow-up Work
+
+
On the deployment host, pull and rebuild target images:
+
+
cd /home/delta/islandflow
+git pull --ff-only
+docker compose -f deployment/docker/docker-compose.yml build candles web ingest-equities
+
+
If this recurs after dependency changes, run and commit:
+
+
bun run sync:docker-workspace
+bun run check:docker-workspace
+
+
Tracking issue: islandflow-xll.
+
+
+
From 803740190cfea754f97fa031a2032a22e5eadf25 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 15 May 2026 19:04:15 -0400
Subject: [PATCH 011/107] chore(deploy): preflight docker workspace snapshot
sync
---
.beads/issues.jsonl | 1 +
bun.lock | 1 +
deployment/docker/workspace-root/bun.lock | 1 +
...ploy-preflight-docker-workspace-check.html | 83 ++++++++++++++
scripts/deploy.ts | 103 ++++++++++++++----
5 files changed, 166 insertions(+), 23 deletions(-)
create mode 100644 docs/turns/2026-05-15-deploy-preflight-docker-workspace-check.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index ead6db3..f2c75f6 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,3 +1,4 @@
+{"_type":"issue","id":"islandflow-k4f","title":"Gate deploy script on docker workspace snapshot sync","description":"Prevent frozen-lockfile build failures during deploy by adding a local preflight in scripts/deploy.ts that runs bun run check:docker-workspace and aborts with a clear sync+commit remediation message when stale.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:01:44Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:04:11Z","started_at":"2026-05-15T23:01:48Z","closed_at":"2026-05-15T23:04:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-xll","title":"Fix bun.lock drift causing frozen-lockfile Docker build failures","description":"Docker image builds fail in multiple targets (candles, web, ingest services) because bun install --frozen-lockfile detects lockfile changes. Update workspace lockfile to match manifests and verify frozen install succeeds.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T22:52:38Z","created_by":"dirtydishes","updated_at":"2026-05-15T22:55:23Z","started_at":"2026-05-15T22:52:40Z","closed_at":"2026-05-15T22:55:23Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-9nd","title":"Hosted synthetic tape redesign with internal control surface","description":"Implement hosted synthetic market redesign with shared deterministic regime engine, internal JetStream KV control plane, ingest coupling across options and equities, and an internal bottom-right synthetic-control drawer with Next proxy routes. Preserve the six public smart-money categories while adding hidden subtype families, soft coverage accounting, and backend-only admin endpoints.\n","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T01:25:02Z","created_by":"dirtydishes","updated_at":"2026-05-14T02:10:03Z","started_at":"2026-05-14T01:25:09Z","closed_at":"2026-05-14T02:10:03Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-9dz","title":"Tune synthetic smart-money scenario coverage","description":"Redesign synthetic smart-money option prints so the emitted scenarios trigger each classifier category more consistently while staying directionally plausible. Focus on scenario mix, DTE/moneyness, price placement, and event/structure context so the Electron demo reliably shows institutional directional, retail whale, event-driven, vol seller, arbitrage, and hedge reactive hits.\n","status":"in_progress","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-13T21:36:37Z","created_by":"dirtydishes","updated_at":"2026-05-13T21:36:41Z","started_at":"2026-05-13T21:36:41Z","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/bun.lock b/bun.lock
index c660953..46160a7 100644
--- a/bun.lock
+++ b/bun.lock
@@ -39,6 +39,7 @@
"packages/bus": {
"name": "@islandflow/bus",
"dependencies": {
+ "@islandflow/types": "workspace:*",
"nats": "^2.24.0",
},
},
diff --git a/deployment/docker/workspace-root/bun.lock b/deployment/docker/workspace-root/bun.lock
index c660953..46160a7 100644
--- a/deployment/docker/workspace-root/bun.lock
+++ b/deployment/docker/workspace-root/bun.lock
@@ -39,6 +39,7 @@
"packages/bus": {
"name": "@islandflow/bus",
"dependencies": {
+ "@islandflow/types": "workspace:*",
"nats": "^2.24.0",
},
},
diff --git a/docs/turns/2026-05-15-deploy-preflight-docker-workspace-check.html b/docs/turns/2026-05-15-deploy-preflight-docker-workspace-check.html
new file mode 100644
index 0000000..fbeb67d
--- /dev/null
+++ b/docs/turns/2026-05-15-deploy-preflight-docker-workspace-check.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+ Turn Report - 2026-05-15 - Deploy preflight docker workspace check
+
+
+
+
Turn Report: Deploy script preflight guard for Docker workspace snapshot
+
Date/Time: 2026-05-15 19:03:09 EDT
+
+
Summary
+
+ Updated scripts/deploy.ts so ./deploy now fails fast when
+ deployment/docker/workspace-root is stale. The script now runs
+ bun run check:docker-workspace during local prechecks and prints a clear remediation
+ message to run sync + commit before deployment.
+
+
+
Changes Made
+
+
Created localWorkspaceSnapshotPrecheck() in scripts/deploy.ts.
+
Added preflight invocation to both deployment modes:
+
+
localMainPrecheck()
+
localBranchPrecheck()
+
+
+
On failure, deploy now exits with an explicit message:
+
+
Refusing deploy: deployment/docker/workspace-root is out of sync.
+Run bun run sync:docker-workspace, commit updated snapshot files, then retry deploy.
+
+
Refreshed lock state to keep checks green:
+
+
bun.lock
+
deployment/docker/workspace-root/bun.lock
+
+
+
+
+
Context
+
+ The deployment compose stack builds from a snapshot under
+ deployment/docker/workspace-root. If that snapshot drifts from the active
+ workspace graph, Docker build-time bun install --frozen-lockfile fails remotely.
+ This change catches drift locally before any remote rollout starts.
+
+
+
Important Implementation Details
+
+
Preflight uses spawnSync("bun", ["run", "check:docker-workspace"]) with inherited stdio for transparent output.
+
Failure exits with the same non-zero status, preserving script CI/shell behavior.
+
Guard applies to both ./deploy main and ./deploy current-branch flows.
+
+
+
Validation
+
+
Passed: bun run scripts/deploy.ts --help
+
Passed: bun run check:docker-workspace (after lock sync)
+
+
+
Issues, Limitations, and Mitigations
+
+
Limitation: Did not execute a full remote deploy during this turn.
+
Mitigation: The guard is in the local precheck path, so next real deploy run will enforce the new check automatically.
+
+
+
Follow-up Work
+
+
Optional defense-in-depth: run bun run check:docker-workspace on the server in remote rollout before docker compose up -d --build.
+
Optional CI gate: add bun run check:docker-workspace to PR checks to prevent stale snapshots reaching main.
+
Beads issue: islandflow-k4f.
+
+
+
diff --git a/scripts/deploy.ts b/scripts/deploy.ts
index 87abd52..b76a393 100644
--- a/scripts/deploy.ts
+++ b/scripts/deploy.ts
@@ -11,16 +11,32 @@ const REMOTE_HOST = "delta@152.53.80.229";
const REMOTE_REPO = "/home/delta/islandflow";
const REMOTE_DEPLOYMENT = "/home/delta/islandflow/deployment/docker";
const SSH_KEY = path.join(process.env.HOME ?? "", ".ssh", "delta_ed25519");
-const SSH_OPTIONS = ["-i", SSH_KEY, "-o", "IdentitiesOnly=yes", "-o", "BatchMode=yes"];
+const SSH_OPTIONS = [
+ "-i",
+ SSH_KEY,
+ "-o",
+ "IdentitiesOnly=yes",
+ "-o",
+ "BatchMode=yes",
+];
const ALLOWED_REMOTE_UNTRACKED = new Set([
"deployment/docker/signal-cli-0.14.3-Linux-native.tar.gz",
- "deployment/npm/"
+ "deployment/npm/",
]);
const API_CONTAINER = "islandflow-vps-api-1";
const WEB_CONTAINER = "islandflow-vps-web-1";
-const PUBLIC_APP_URL = process.env.DEPLOY_PUBLIC_APP_URL?.trim() || "https://flow.deltaisland.io";
-const PUBLIC_API_HEALTH_URL = process.env.DEPLOY_PUBLIC_API_HEALTH_URL?.trim() || null;
-const LOG_SERVICES = ["api", "web", "compute", "candles", "ingest-options", "ingest-equities"];
+const PUBLIC_APP_URL =
+ process.env.DEPLOY_PUBLIC_APP_URL?.trim() || "https://flow.deltaisland.io";
+const PUBLIC_API_HEALTH_URL =
+ process.env.DEPLOY_PUBLIC_API_HEALTH_URL?.trim() || null;
+const LOG_SERVICES = [
+ "api",
+ "web",
+ "compute",
+ "candles",
+ "ingest-options",
+ "ingest-equities",
+];
const scriptPath = fileURLToPath(import.meta.url);
const repoRoot = path.resolve(path.dirname(scriptPath), "..");
@@ -55,12 +71,16 @@ function formatCommand(command: string, args: string[]): string {
.join(" ");
}
-function runChecked(command: string, args: string[], options: SpawnSyncOptions = {}): void {
+function runChecked(
+ command: string,
+ args: string[],
+ options: SpawnSyncOptions = {},
+): void {
console.log(`$ ${formatCommand(command, args)}`);
const result = spawnSync(command, args, {
cwd: repoRoot,
stdio: "inherit",
- ...options
+ ...options,
});
if (result.status !== 0) {
@@ -68,12 +88,16 @@ function runChecked(command: string, args: string[], options: SpawnSyncOptions =
}
}
-function captureChecked(command: string, args: string[], options: SpawnSyncOptions = {}): string {
+function captureChecked(
+ command: string,
+ args: string[],
+ options: SpawnSyncOptions = {},
+): string {
const result = spawnSync(command, args, {
cwd: repoRoot,
encoding: "utf8",
stdio: ["inherit", "pipe", "pipe"],
- ...options
+ ...options,
});
if (result.status !== 0) {
@@ -84,7 +108,11 @@ function captureChecked(command: string, args: string[], options: SpawnSyncOptio
return result.stdout ?? "";
}
-function runRemoteScript(title: string, script: string, args: string[] = []): void {
+function runRemoteScript(
+ title: string,
+ script: string,
+ args: string[] = [],
+): void {
section(title);
const sshArgs = [...SSH_OPTIONS, REMOTE_HOST, "bash", "-s", "--", ...args];
console.log(`$ ${formatCommand("ssh", sshArgs)}`);
@@ -92,7 +120,7 @@ function runRemoteScript(title: string, script: string, args: string[] = []): vo
cwd: repoRoot,
input: script,
encoding: "utf8",
- stdio: ["pipe", "inherit", "inherit"]
+ stdio: ["pipe", "inherit", "inherit"],
});
if (result.status !== 0) {
@@ -100,7 +128,10 @@ function runRemoteScript(title: string, script: string, args: string[] = []): vo
}
}
-function parseArgs(rawArgs: string[]): { mode: DeployMode; forceRecreate: boolean } {
+function parseArgs(rawArgs: string[]): {
+ mode: DeployMode;
+ forceRecreate: boolean;
+} {
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
usage(0);
}
@@ -114,7 +145,9 @@ function parseArgs(rawArgs: string[]): { mode: DeployMode; forceRecreate: boolea
if (
(positional.length === 1 && positional[0] === "current-branch") ||
- (positional.length === 2 && positional[0] === "current" && positional[1] === "branch")
+ (positional.length === 2 &&
+ positional[0] === "current" &&
+ positional[1] === "branch")
) {
return { mode: "current-branch", forceRecreate };
}
@@ -129,12 +162,28 @@ function assertSshKeyExists(): void {
}
}
+function localWorkspaceSnapshotPrecheck(): void {
+ console.log("$ bun run check:docker-workspace");
+ const result = spawnSync("bun", ["run", "check:docker-workspace"], {
+ cwd: repoRoot,
+ stdio: "inherit",
+ });
+
+ if (result.status !== 0) {
+ console.error(
+ "Refusing deploy: deployment/docker/workspace-root is out of sync. Run `bun run sync:docker-workspace`, commit updated snapshot files, then retry deploy.",
+ );
+ process.exit(result.status ?? 1);
+ }
+}
+
function localMainPrecheck(): void {
section("Local Precheck");
runChecked("git", ["fetch", "origin"]);
runChecked("git", ["status", "--short", "--branch"]);
runChecked("git", ["rev-parse", "--verify", "HEAD"]);
runChecked("git", ["rev-parse", "origin/main"]);
+ localWorkspaceSnapshotPrecheck();
}
function currentBranchName(): string {
@@ -155,10 +204,12 @@ function localBranchPrecheck(branch: string): void {
const porcelain = captureChecked("git", ["status", "--porcelain=v1"]).trim();
if (porcelain) {
console.error(
- `Refusing to deploy ${branch} with uncommitted local changes. Commit the intended state first.`
+ `Refusing to deploy ${branch} with uncommitted local changes. Commit the intended state first.`,
);
process.exit(1);
}
+
+ localWorkspaceSnapshotPrecheck();
}
function publishCurrentBranch(branch: string): void {
@@ -169,8 +220,8 @@ function publishCurrentBranch(branch: string): void {
{
cwd: repoRoot,
encoding: "utf8",
- stdio: ["inherit", "pipe", "pipe"]
- }
+ stdio: ["inherit", "pipe", "pipe"],
+ },
);
if (upstreamResult.status === 0) {
@@ -218,12 +269,18 @@ while IFS= read -r line; do
;;
esac
done <<< "$status"
-`
+`,
);
}
-function remoteRollout(mode: DeployMode, branch: string | null, forceRecreate: boolean): void {
- const composeArgs = forceRecreate ? "up -d --build --force-recreate" : "up -d --build";
+function remoteRollout(
+ mode: DeployMode,
+ branch: string | null,
+ forceRecreate: boolean,
+): void {
+ const composeArgs = forceRecreate
+ ? "up -d --build --force-recreate"
+ : "up -d --build";
const switchCommand =
mode === "main"
? `git switch main
@@ -242,7 +299,7 @@ ${switchCommand}
cd "${REMOTE_DEPLOYMENT}"
docker compose ${composeArgs}
-`
+`,
);
}
@@ -257,7 +314,7 @@ docker compose ps
docker compose logs --tail=100 ${LOG_SERVICES.join(" ")}
docker exec ${API_CONTAINER} bun -e 'const r = await fetch("http://127.0.0.1:4000/health"); console.log(await r.text())'
docker exec ${WEB_CONTAINER} bun -e 'const r = await fetch("http://127.0.0.1:3000/"); console.log(r.status)'
-`
+`,
);
}
@@ -271,7 +328,7 @@ function publicVerification(): void {
}
console.log(
- "Skipping separate public API health check; same-origin mode relies on the public app check plus container-local API verification."
+ "Skipping separate public API health check; same-origin mode relies on the public app check plus container-local API verification.",
);
}
@@ -293,7 +350,7 @@ function main(): void {
console.log(
mode === "main"
? "Deploying origin/main to the existing Islandflow VPS checkout."
- : "Deploying the current local branch to the existing Islandflow VPS checkout."
+ : "Deploying the current local branch to the existing Islandflow VPS checkout.",
);
if (mode === "main") {
From 73715c8163e175dd14bdb07b6906a17607a72ade Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 15 May 2026 20:44:08 -0400
Subject: [PATCH 012/107] Restore CLAUDE.md
---
.beads/issues.jsonl | 3 +
CLAUDE.md | 171 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 174 insertions(+)
create mode 100644 CLAUDE.md
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index f2c75f6..d601adc 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -10,6 +10,8 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-qh7","title":"Implement dual-runtime deploy workflow with partial deploys","description":"Implement the planned refactor of the root deploy script and scripts/deploy.ts so deployment can target Docker and host-native runtimes during a transition period. Preserve local dev as Docker infra plus native Bun services/web, add explicit runtime selection, runtime-specific prechecks/rollout/verification, and support partial deploy scopes such as web-only or services-only. Update operator documentation for the new workflow.","notes":"Implemented dual-runtime deploy workflow. scripts/deploy.ts now supports --runtime docker|native, scope flags (--web-only, --api-only, --services-only), and --no-build. Docker verification now uses docker compose exec instead of hardcoded container names. Added deployment/native/README.md and updated README.md plus deployment/docker/README.md for the new workflow. Validation: bun run scripts/deploy.ts --help, bun run check:docker-workspace, guard checks for invalid flag combinations.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:38:31Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:17Z","started_at":"2026-05-15T23:40:13Z","closed_at":"2026-05-15T23:46:17Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-iiy","title":"Plan deploy workflow changes for Docker/native transition","description":"User requested a repo-specific plan for updating the root deploy script and deployment workflow to support Docker/native transition paths, faster local iteration, and partial deploy modes. This task covers confirming the target workflow, documenting current assumptions, and producing an implementation-ready plan without changing implementation files.","notes":"Confirmed transition strategy: local dev stays Docker-infra-only plus native Bun services/web; VPS deploy path should support both Docker and host-native runtimes during transition; partial deploys are desired; current main/current-branch modes may evolve. Produced an implementation-ready plan covering current assumptions, runtime split, CLI shape, prechecks, rollout, verification, rollback, docs, and validation scenarios. Follow-up implementation tracked in islandflow-qh7.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:37:28Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:38:41Z","started_at":"2026-05-15T23:37:30Z","closed_at":"2026-05-15T23:38:41Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-wab","title":"Quiet the terminal view chrome","description":"The Islandflow terminal view currently carries too much chrome intensity: strong shell gradients, visible grid texture, active amber wash, glassy overlays, and heavily styled drawer/filter surfaces compete with live data. Refine the product UI so the terminal feels calmer and more forensic while preserving status clarity, scan speed, and identity. Focus on reducing decorative contrast, flattening surfaces, and making accents scarcer without weakening affordances.","notes":"Refined terminal chrome in apps/web/app/globals.css: moved shell tokens to quieter OKLCH values, removed grid texture, flattened panes/overlays, reduced active amber wash, softened classified row treatment, and added reduced-motion handling for the connecting pulse. Validation: bun test apps/web/app/terminal.test.ts; bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T12:05:25Z","created_by":"dirtydishes","updated_at":"2026-05-15T12:13:10Z","started_at":"2026-05-15T12:05:30Z","closed_at":"2026-05-15T12:13:10Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-hio","title":"Add Pi /plan command for plan mode","description":"Create a Pi extension so typing /plan activates plan mode instructions and guards against implementation file edits until disabled.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T04:56:00Z","created_by":"dirtydishes","updated_at":"2026-05-15T04:57:03Z","started_at":"2026-05-15T04:56:03Z","closed_at":"2026-05-15T04:57:03Z","close_reason":"Implemented project-local Pi /plan extension with plan-mode guardrails.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-t8s","title":"Reconcile merge conflicts on impeccable","description":"Resolve the PR branch conflicts against main while preserving terminal hardening, responsive adaptation, and related test coverage.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T22:32:40Z","created_by":"dirtydishes","updated_at":"2026-05-14T22:34:03Z","started_at":"2026-05-14T22:33:05Z","closed_at":"2026-05-14T22:34:03Z","close_reason":"Rebased impeccable onto main, resolved the terminal test conflict, and revalidated the web app.","dependency_count":0,"dependent_count":0,"comment_count":0}
@@ -30,4 +32,5 @@
{"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-38p","title":"Add native deployment unit templates and rollback helpers","description":"The deploy helper now supports --runtime native, but the repo still relies on operator-managed systemd units and manual rollback. Add checked-in native deployment templates or provisioning guidance for the expected units, and consider lightweight rollback/smoke-test helpers once the host-native path is exercised on the real VPS.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:46:42Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-575","title":"Document smart-money event calendar env","description":"Document smart-money event-calendar environment configuration in env examples and README.\n","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T06:57:14Z","created_by":"dirtydishes","updated_at":"2026-05-05T06:57:57Z","started_at":"2026-05-05T06:57:17Z","closed_at":"2026-05-05T06:57:57Z","close_reason":"Documented event-calendar env variables","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..351b68c
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,171 @@
+
+## Beads Issue Tracker
+
+This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands.
+
+### Quick Reference
+
+```bash
+bd ready # Find available work
+bd show # View issue details
+bd update --claim # Claim work
+bd close # Complete work
+```
+
+### Rules
+
+- Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists
+- Run `bd prime` for detailed command reference and session close protocol
+- Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files
+
+## Session Completion
+
+**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
+
+**MANDATORY WORKFLOW:**
+
+1. **File issues for remaining work** - Create issues for anything that needs follow-up
+2. **Run quality gates** (if code changed) - Tests, linters, builds
+3. **Update issue status** - Close finished work, update in-progress items
+4. **PUSH TO REMOTE** - This is MANDATORY:
+ ```bash
+ git pull --rebase
+ bd dolt push
+ git push
+ git status # MUST show "up to date with origin"
+ ```
+5. **Clean up** - Clear stashes, prune remote branches
+6. **Verify** - All changes committed AND pushed
+7. **Hand off** - Provide context for next session
+
+**CRITICAL RULES:**
+- Work is NOT complete until `git push` succeeds
+- NEVER stop before pushing - that leaves work stranded locally
+- NEVER say "ready to push when you are" - YOU must push
+- If push fails, resolve and retry until it succeeds
+
+
+## Minimal Repo Operating Instructions
+
+This is a Bun + TypeScript monorepo for an event-sourced market-data pipeline:
+- Flow: ingest services publish to NATS/JetStream, compute/candles derive events, API serves REST/WS, web consumes live/replay streams.
+- Main folders: `services/*` (runtime services), `packages/*` (shared libs/types/storage), `apps/web` (Next.js UI).
+- Infra dependency: local dev assumes Docker services (NATS, ClickHouse, Redis) are available.
+
+Use these repo-specific commands:
+- Install deps: `bun install`
+- Start full stack: `bun run dev`
+- Start infra only: `bun run dev:infra`
+- Start backend services only: `bun run dev:services`
+- Start web only: `bun run dev:web`
+
+Testing and validation in this repo are Bun-first:
+- Run tests: `bun test`
+- Run scoped tests: `bun test services/compute/tests` (or another package/service path)
+- Validate web production build when UI code changes: `bun --cwd=apps/web run build`
+
+Working style that avoids common problems here:
+- Prefer editing in the touched workspace (`services/`, `packages/`, `apps/web`) and keep shared contract changes in `packages/types`.
+- Keep `.env` aligned with `.env.example`; adapters default to synthetic modes for local development.
+- Dev runners persist child PID state in `.tmp/`; if a previous run crashed, restart via the standard `bun run dev*` commands so stale processes are cleaned up.
+
+## Required Turn Documentation
+
+At the end of every completed implementation task, before final handoff, create a user-readable HTML document describing the work.
+
+This documentation is mandatory whenever code, configuration, tests, or project files were changed.
+
+### Location
+
+Save the document in:
+
+```text
+docs/turns/
+```
+
+Use a clear timestamped filename:
+
+```text
+docs/turns/YYYY-MM-DD-short-task-name.html
+```
+
+Example:
+
+```text
+docs/turns/2026-05-14-add-market-replay-controls.html
+```
+
+### Format
+
+Use the impeccable skill to structure the document as clean, readable HTML.
+
+If the impeccable skill is unavailable, still create a well-structured standalone HTML file with:
+
+- A concise summary at the top
+- A detailed explanation of what changed
+- Relevant context or background
+- Specific code snippets or examples when helpful
+- Issues, limitations, tradeoffs, or mitigations
+- Validation performed, including tests, builds, linters, or manual checks
+- Any remaining follow-up work, with corresponding Beads issue IDs when applicable
+
+### Required Sections
+
+Each turn document must include these sections:
+
+1. **Summary**
+2. **Changes Made**
+3. **Context**
+4. **Important Implementation Details**
+5. **Validation**
+6. **Issues, Limitations, and Mitigations**
+7. **Follow-up Work**
+
+### Completion Rule
+
+A task is not complete until:
+
+1. The Beads workflow is updated
+2. The turn document is created in `docs/turns`
+3. Relevant quality gates have passed or failures are documented
+4. Changes are committed
+5. `bd dolt push` succeeds
+6. `git push` succeeds
+7. `git status` shows the branch is up to date with origin
+
+For trivial changes, the document may be brief, but it must still exist and clearly explain what changed and how it was validated.
+
+## Plan Mode Documentation
+
+When working in plan mode, do not modify implementation files.
+
+At the end of plan mode, provide a concise summary of the plan and ask the user whether they want to proceed with implementation.
+
+If the user asks to save the plan, create a user-readable HTML plan document in:
+
+```text
+docs/plans/
+```
+
+Use a clear timestamped filename:
+
+```text
+docs/plans/YYYY-MM-DD-short-plan-name.html
+```
+
+The plan document should be labeled clearly as a plan and should include:
+
+1. **Plan Summary**
+2. **Goals**
+3. **Proposed Changes**
+4. **Relevant Context**
+5. **Implementation Steps**
+6. **Risks, Limitations, and Mitigations**
+7. **Open Questions**
+
+Always do the following when you finish a task, finish the beads workflow and and make a commit:
+- Document the changes in a user-readable format
+- Use the impeccable skill to structure the document as HTML
+- Create a clear, concise summary of the changes at the top, followed by a detailed description of the changes, including any relevant context or background as well as specific code snippets or examples.
+- Note any relevant issues or limitations that were addressed or mitigated by the changes.
+- The HTML file should be stored in the `docs/turns` directory. It should include the current date and time, as well as a brief explanation of changes. e.g. docs/turns/YYYY-MM-DD-{description}.html
From df49af1ba25645a0b82c7488f3630bcc51121bf8 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 15 May 2026 19:47:09 -0400
Subject: [PATCH 013/107] Add dual-runtime deploy workflow
---
README.md | 17 +
deployment/docker/README.md | 27 +-
deployment/native/README.md | 122 ++++
...26-05-15-dual-runtime-deploy-workflow.html | 170 ++++++
scripts/deploy.ts | 561 +++++++++++++++---
5 files changed, 795 insertions(+), 102 deletions(-)
create mode 100644 deployment/native/README.md
create mode 100644 docs/turns/2026-05-15-dual-runtime-deploy-workflow.html
diff --git a/README.md b/README.md
index e0848ef..3542353 100644
--- a/README.md
+++ b/README.md
@@ -116,6 +116,23 @@ Start web only:
- `bun run dev:web`
+Recommended fast iteration loop:
+
+- `bun run dev:infra` for Docker-backed infra only
+- `bun run dev:services` for native Bun backend services
+- `bun run dev:web` for the local Next.js UI
+
+This keeps Docker in the local workflow where it helps most (NATS, ClickHouse, Redis) without forcing the app services themselves into slower container rebuild/restart loops.
+
+## Deployment Workflow
+
+- `./deploy main` keeps the current VPS Docker rollout path as the default.
+- `./deploy main --runtime native` targets a host-native Bun + systemd deployment.
+- `./deploy current-branch` and `./deploy current-branch --runtime native` keep branch deploys available during the transition.
+- Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, and `--no-build`.
+- Docker runtime details live in `deployment/docker/README.md`.
+- Native runtime expectations live in `deployment/native/README.md`.
+
## Desktop Shell
Islandflow also includes a thin Electron desktop shell in `apps/desktop`.
diff --git a/deployment/docker/README.md b/deployment/docker/README.md
index 52e8198..426a006 100644
--- a/deployment/docker/README.md
+++ b/deployment/docker/README.md
@@ -1,8 +1,13 @@
# Docker Deployment
-This directory is the supported VPS deployment path for Islandflow.
+This directory contains the Docker runtime for Islandflow VPS deployments.
-The repo no longer ships or supports a separate `deployment/npm` stack. Docker Compose is the deployment surface; if you want a reverse proxy, point it at the host ports published by this stack.
+Docker remains the default server rollout path, but the repo-root `deploy` helper can now target either:
+
+- `--runtime docker` for this Docker Compose stack
+- `--runtime native` for a host-native Bun + systemd rollout described in `deployment/native/README.md`
+
+The repo no longer ships or supports a separate `deployment/npm` stack. If you want a reverse proxy, point it at the host ports published by this stack.
It is separate from the repo-root `docker-compose.yml`, which remains the lightweight local infra stack for development.
@@ -198,6 +203,7 @@ It preserves the current Docker Compose project and avoids destructive cleanup o
```bash
./deploy main
+./deploy main --runtime docker
```
This flow:
@@ -213,6 +219,7 @@ This flow:
```bash
./deploy current-branch
+./deploy current-branch --runtime docker
```
Alias:
@@ -229,13 +236,24 @@ This flow:
- switches the server checkout to that same branch and keeps it there until you intentionally move it back
- runs the same rebuild and verification steps as `main`
+### Partial Docker rollouts
+
+Examples:
+
+```bash
+./deploy main --runtime docker --web-only
+./deploy main --runtime docker --api-only
+./deploy current-branch --runtime docker --services-only
+./deploy main --runtime docker --web-only --no-build
+```
+
### Escalation path
Use force recreate only when a normal refresh does not update the services cleanly:
```bash
-./deploy main --force-recreate
-./deploy current-branch --force-recreate
+./deploy main --runtime docker --force-recreate
+./deploy current-branch --runtime docker --force-recreate
```
### Return the server to `main`
@@ -244,6 +262,7 @@ If the live checkout is on a branch deploy and you want normal production tracki
```bash
./deploy main
+./deploy main --runtime docker
```
The helper always does the final public verification against:
diff --git a/deployment/native/README.md b/deployment/native/README.md
new file mode 100644
index 0000000..fed5b74
--- /dev/null
+++ b/deployment/native/README.md
@@ -0,0 +1,122 @@
+# Native Deployment
+
+This directory documents the host-native Islandflow rollout path used by:
+
+```bash
+./deploy main --runtime native
+./deploy current-branch --runtime native
+```
+
+This runtime is intended for faster server iteration during the transition away from Docker-only app rollouts. Local development should still prefer:
+
+- Docker for infra (`bun run dev:infra`)
+- native Bun services (`bun run dev:services`)
+- native Next.js web (`bun run dev:web`)
+
+## What native deploy means here
+
+The checked-in `deploy` helper assumes:
+
+- the live repo checkout is still `/home/delta/islandflow`
+- Bun is installed on the VPS
+- app processes are managed by `systemd`
+- infrastructure services such as NATS, ClickHouse, and Redis are already reachable from the host
+- the web app runs from `apps/web` and is served with `next start -p 3000`
+
+The deploy script updates the repo checkout, optionally runs `bun install --frozen-lockfile`, optionally rebuilds the web app, restarts the target systemd units, and then verifies the services locally on the VPS plus through the public app URL.
+
+## Expected unit names
+
+Default unit names used by `scripts/deploy.ts`:
+
+- `islandflow-web`
+- `islandflow-api`
+- `islandflow-compute`
+- `islandflow-candles`
+- `islandflow-ingest-options`
+- `islandflow-ingest-equities`
+
+Override them from your local shell before running `./deploy` if the server uses different names:
+
+```bash
+export DEPLOY_NATIVE_WEB_UNIT=my-web-unit
+export DEPLOY_NATIVE_API_UNIT=my-api-unit
+```
+
+Available overrides:
+
+- `DEPLOY_NATIVE_WEB_UNIT`
+- `DEPLOY_NATIVE_API_UNIT`
+- `DEPLOY_NATIVE_COMPUTE_UNIT`
+- `DEPLOY_NATIVE_CANDLES_UNIT`
+- `DEPLOY_NATIVE_INGEST_OPTIONS_UNIT`
+- `DEPLOY_NATIVE_INGEST_EQUITIES_UNIT`
+
+## systemctl invocation
+
+By default the deploy helper uses:
+
+```bash
+sudo systemctl
+```
+
+If the server uses user units or another wrapper, override it locally before invoking `./deploy`:
+
+```bash
+export DEPLOY_NATIVE_SYSTEMCTL_PREFIX="systemctl --user"
+./deploy main --runtime native
+```
+
+## Partial native rollouts
+
+Examples:
+
+```bash
+./deploy main --runtime native --web-only
+./deploy main --runtime native --api-only
+./deploy current-branch --runtime native --services-only
+./deploy main --runtime native --web-only --no-build
+```
+
+Scope behavior:
+
+- default: restart web + API + backend services
+- `--web-only`: rebuild/restart only the web unit
+- `--api-only`: restart only the API unit
+- `--services-only`: restart API + backend units without touching the web unit
+- `--no-build`: skip `bun install --frozen-lockfile` and skip the web build step
+
+## Server preparation checklist
+
+Before the first native rollout, ensure the VPS has:
+
+1. Bun installed and on `PATH`
+2. a working `/home/delta/islandflow/.env` (or unit-managed equivalent env source)
+3. systemd units for each target service
+4. the web unit configured to serve the built app on port `3000`
+5. the API unit configured to serve health checks on port `4000`
+6. infrastructure endpoints configured so the native services can reach NATS, ClickHouse, and Redis
+
+## Verification
+
+Native deploys verify:
+
+- target units are active via `systemctl`
+- recent unit status and journal output can be collected
+- local `http://127.0.0.1:4000/health` when API scope is included
+- local `http://127.0.0.1:3000/` when web scope is included
+- the public app URL from the local machine after the rollout finishes
+
+## Rollback
+
+Rollback remains manual for now:
+
+1. switch the server checkout back to the last known-good branch or commit
+2. rerun the appropriate native deploy command
+3. if needed, restart only the affected units with `systemctl`
+
+Docker remains available as the fallback runtime during the transition:
+
+```bash
+./deploy main --runtime docker
+```
diff --git a/docs/turns/2026-05-15-dual-runtime-deploy-workflow.html b/docs/turns/2026-05-15-dual-runtime-deploy-workflow.html
new file mode 100644
index 0000000..7fe2a42
--- /dev/null
+++ b/docs/turns/2026-05-15-dual-runtime-deploy-workflow.html
@@ -0,0 +1,170 @@
+
+
+
+
+
+ 2026-05-15: Dual-runtime deploy workflow
+
+
+
+
+
+ Updated the root deploy flow so it can target either the existing Docker Compose VPS runtime or a new host-native Bun + systemd runtime, while also adding partial deploy scopes for faster iteration.
+
+
+
+
Summary
+
+ The deploy helper now supports --runtime docker and --runtime native, keeps Docker as the default, and adds --web-only, --api-only, --services-only, and --no-build. Documentation now clearly separates fast local development from VPS rollout options.
+
+
+
+
+
Changes Made
+
+
Refactored scripts/deploy.ts into shared git/publish logic plus runtime-specific precheck, rollout, and verification paths.
+
Removed Docker verification’s dependence on hardcoded container names and switched to docker compose exec.
+
Added native deployment support that assumes Bun plus systemd-managed units on the VPS.
+
Added a new operator guide at deployment/native/README.md.
+
Updated README.md to emphasize the preferred fast local loop: Docker infra only, native Bun services, native web dev.
+
Updated deployment/docker/README.md to document Docker as the default runtime and show new partial rollout examples.
+
+
+
+
+
Context
+
+ The repo already separated local infra from application processes: the root docker-compose.yml is infra-only, while services and the web app run through Bun scripts. The old deploy helper still assumed every server rollout was Docker-only. This change makes the deploy workflow match the new operating model: fast native iteration locally, Docker still available in production, and a native VPS path available during transition.
+
+
+
+
+
Important Implementation Details
+
+
Docker remains the default runtime, so ./deploy main still works without extra flags.
+
Native rollouts are invoked with ./deploy main --runtime native or ./deploy current-branch --runtime native.
+
Partial scopes are mutually exclusive and intentionally simple:
+
+
./deploy main --runtime docker --web-only
+./deploy main --runtime native --api-only
+./deploy current-branch --runtime docker --services-only
+./deploy main --runtime native --web-only --no-build
+
+
Docker workspace snapshot validation now runs only when a Docker rollout will build images.
+
Native rollouts assume systemd unit names like islandflow-web and islandflow-api, but those names can be overridden with environment variables such as DEPLOY_NATIVE_WEB_UNIT.
+
The native path also allows overriding the systemctl wrapper via DEPLOY_NATIVE_SYSTEMCTL_PREFIX, which is useful for systemctl --user setups.
+
+
+
+
+
Validation
+
+
Passed: bun run scripts/deploy.ts --help
+
Passed: bun run check:docker-workspace
+
Passed: invalid-flag guard for --runtime native --force-recreate
+
Passed: conflicting-scope guard for --web-only --api-only
+
+
bun run scripts/deploy.ts --help
+bun run check:docker-workspace
+bun run scripts/deploy.ts main --runtime native --force-recreate
+bun run scripts/deploy.ts main --web-only --api-only
+
+
+
+
Issues, Limitations, and Mitigations
+
+
Native deploys assume server-side systemd units already exist. Mitigation: added deployment/native/README.md documenting expected unit names and override variables.
+
Rollback is still manual. Mitigation: both Docker and native docs now frame runtime selection as a transition path, with Docker preserved as a fallback.
+
No native service unit files were added in this change. This keeps the scope focused on the deploy workflow itself.
+
Public verification still centers on the hosted app URL. API verification remains local-to-runtime unless DEPLOY_PUBLIC_API_HEALTH_URL is configured.
+
+
+
+
+
Follow-up Work
+
+
Implementation tracked in islandflow-qh7 is complete for the deploy helper itself.
+
Open follow-up: islandflow-38p, add checked-in native deployment unit templates and rollback helpers.
+
+
+
+
+
diff --git a/scripts/deploy.ts b/scripts/deploy.ts
index b76a393..f56598d 100644
--- a/scripts/deploy.ts
+++ b/scripts/deploy.ts
@@ -6,10 +6,20 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
type DeployMode = "main" | "current-branch";
+type DeployRuntime = "docker" | "native";
+type DeployScope = "full" | "web" | "api" | "services";
+
+type DeployOptions = {
+ mode: DeployMode;
+ runtime: DeployRuntime;
+ scope: DeployScope;
+ forceRecreate: boolean;
+ noBuild: boolean;
+};
const REMOTE_HOST = "delta@152.53.80.229";
const REMOTE_REPO = "/home/delta/islandflow";
-const REMOTE_DEPLOYMENT = "/home/delta/islandflow/deployment/docker";
+const REMOTE_DOCKER_DEPLOYMENT = "/home/delta/islandflow/deployment/docker";
const SSH_KEY = path.join(process.env.HOME ?? "", ".ssh", "delta_ed25519");
const SSH_OPTIONS = [
"-i",
@@ -17,47 +27,83 @@ const SSH_OPTIONS = [
"-o",
"IdentitiesOnly=yes",
"-o",
- "BatchMode=yes",
+ "BatchMode=yes"
];
const ALLOWED_REMOTE_UNTRACKED = new Set([
"deployment/docker/signal-cli-0.14.3-Linux-native.tar.gz",
- "deployment/npm/",
+ "deployment/npm/"
]);
-const API_CONTAINER = "islandflow-vps-api-1";
-const WEB_CONTAINER = "islandflow-vps-web-1";
const PUBLIC_APP_URL =
process.env.DEPLOY_PUBLIC_APP_URL?.trim() || "https://flow.deltaisland.io";
const PUBLIC_API_HEALTH_URL =
process.env.DEPLOY_PUBLIC_API_HEALTH_URL?.trim() || null;
-const LOG_SERVICES = [
+const NATIVE_SYSTEMCTL_PREFIX =
+ process.env.DEPLOY_NATIVE_SYSTEMCTL_PREFIX?.trim() || "sudo systemctl";
+const NATIVE_UNITS = {
+ web: process.env.DEPLOY_NATIVE_WEB_UNIT?.trim() || "islandflow-web",
+ api: process.env.DEPLOY_NATIVE_API_UNIT?.trim() || "islandflow-api",
+ compute: process.env.DEPLOY_NATIVE_COMPUTE_UNIT?.trim() || "islandflow-compute",
+ candles: process.env.DEPLOY_NATIVE_CANDLES_UNIT?.trim() || "islandflow-candles",
+ ingestOptions:
+ process.env.DEPLOY_NATIVE_INGEST_OPTIONS_UNIT?.trim() || "islandflow-ingest-options",
+ ingestEquities:
+ process.env.DEPLOY_NATIVE_INGEST_EQUITIES_UNIT?.trim() || "islandflow-ingest-equities"
+} as const;
+const DOCKER_CORE_SERVICES = [
"api",
"web",
"compute",
"candles",
"ingest-options",
- "ingest-equities",
-];
+ "ingest-equities"
+] as const;
+const DOCKER_BACKEND_SERVICES = [
+ "api",
+ "compute",
+ "candles",
+ "ingest-options",
+ "ingest-equities"
+] as const;
const scriptPath = fileURLToPath(import.meta.url);
const repoRoot = path.resolve(path.dirname(scriptPath), "..");
function usage(exitCode = 1): never {
console.error(`Usage:
- ./deploy main [--force-recreate]
- ./deploy current-branch [--force-recreate]
- ./deploy current branch [--force-recreate]
+ ./deploy main [--runtime docker|native] [--web-only|--api-only|--services-only] [--no-build] [--force-recreate]
+ ./deploy current-branch [--runtime docker|native] [--web-only|--api-only|--services-only] [--no-build] [--force-recreate]
+ ./deploy current branch [--runtime docker|native] [--web-only|--api-only|--services-only] [--no-build] [--force-recreate]
Modes:
main Deploy origin/main to the live server checkout.
current-branch Push the current local branch, switch the server to it, and deploy it.
+Runtimes:
+ docker Roll out from deployment/docker with Docker Compose (default).
+ native Roll out host-native Bun services managed by systemd.
+
+Scopes:
+ default Full rollout (web + API + backend services).
+ --web-only Deploy only the Next.js web surface.
+ --api-only Deploy only the API service.
+ --services-only Deploy API + backend services without the web service.
+
Options:
- --force-recreate Escalation path for docker compose when a normal refresh is not enough.
- --help Show this help text.
+ --runtime Explicit runtime selector (docker or native).
+ --no-build Skip docker image builds or native bun install/web build steps.
+ --force-recreate Docker-only escalation path for docker compose when a normal refresh is not enough.
+ --help Show this help text.
Environment:
- DEPLOY_PUBLIC_APP_URL Override the public app URL (default: https://flow.deltaisland.io).
- DEPLOY_PUBLIC_API_HEALTH_URL Optional separate public API health URL for two-origin deployments.`);
+ DEPLOY_PUBLIC_APP_URL Override the public app URL (default: https://flow.deltaisland.io).
+ DEPLOY_PUBLIC_API_HEALTH_URL Optional separate public API health URL for two-origin deployments.
+ DEPLOY_NATIVE_SYSTEMCTL_PREFIX Override systemctl invocation for native rollouts (default: sudo systemctl).
+ DEPLOY_NATIVE_WEB_UNIT Override native web systemd unit name.
+ DEPLOY_NATIVE_API_UNIT Override native api systemd unit name.
+ DEPLOY_NATIVE_COMPUTE_UNIT Override native compute systemd unit name.
+ DEPLOY_NATIVE_CANDLES_UNIT Override native candles systemd unit name.
+ DEPLOY_NATIVE_INGEST_OPTIONS_UNIT Override native ingest-options systemd unit name.
+ DEPLOY_NATIVE_INGEST_EQUITIES_UNIT Override native ingest-equities systemd unit name.`);
process.exit(exitCode);
}
@@ -74,13 +120,13 @@ function formatCommand(command: string, args: string[]): string {
function runChecked(
command: string,
args: string[],
- options: SpawnSyncOptions = {},
+ options: SpawnSyncOptions = {}
): void {
console.log(`$ ${formatCommand(command, args)}`);
const result = spawnSync(command, args, {
cwd: repoRoot,
stdio: "inherit",
- ...options,
+ ...options
});
if (result.status !== 0) {
@@ -91,13 +137,13 @@ function runChecked(
function captureChecked(
command: string,
args: string[],
- options: SpawnSyncOptions = {},
+ options: SpawnSyncOptions = {}
): string {
const result = spawnSync(command, args, {
cwd: repoRoot,
encoding: "utf8",
stdio: ["inherit", "pipe", "pipe"],
- ...options,
+ ...options
});
if (result.status !== 0) {
@@ -111,7 +157,7 @@ function captureChecked(
function runRemoteScript(
title: string,
script: string,
- args: string[] = [],
+ args: string[] = []
): void {
section(title);
const sshArgs = [...SSH_OPTIONS, REMOTE_HOST, "bash", "-s", "--", ...args];
@@ -120,7 +166,7 @@ function runRemoteScript(
cwd: repoRoot,
input: script,
encoding: "utf8",
- stdio: ["pipe", "inherit", "inherit"],
+ stdio: ["pipe", "inherit", "inherit"]
});
if (result.status !== 0) {
@@ -128,28 +174,85 @@ function runRemoteScript(
}
}
-function parseArgs(rawArgs: string[]): {
- mode: DeployMode;
- forceRecreate: boolean;
-} {
+function parseRuntime(rawArgs: string[]): DeployRuntime {
+ for (let index = 0; index < rawArgs.length; index += 1) {
+ const arg = rawArgs[index];
+ if (arg === "--runtime") {
+ const value = rawArgs[index + 1];
+ if (value === "docker" || value === "native") {
+ return value;
+ }
+ usage();
+ }
+
+ if (arg.startsWith("--runtime=")) {
+ const value = arg.slice("--runtime=".length);
+ if (value === "docker" || value === "native") {
+ return value;
+ }
+ usage();
+ }
+ }
+
+ return "docker";
+}
+
+function parseScope(rawArgs: string[]): DeployScope {
+ const scopes = [
+ rawArgs.includes("--web-only") ? "web" : null,
+ rawArgs.includes("--api-only") ? "api" : null,
+ rawArgs.includes("--services-only") ? "services" : null
+ ].filter((value): value is Exclude => value !== null);
+
+ if (scopes.length > 1) {
+ console.error("Choose only one deploy scope flag: --web-only, --api-only, or --services-only.");
+ process.exit(1);
+ }
+
+ return scopes[0] ?? "full";
+}
+
+function parseArgs(rawArgs: string[]): DeployOptions {
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
usage(0);
}
+ const runtime = parseRuntime(rawArgs);
+ const scope = parseScope(rawArgs);
const forceRecreate = rawArgs.includes("--force-recreate");
- const positional = rawArgs.filter((arg) => arg !== "--force-recreate");
+ const noBuild = rawArgs.includes("--no-build");
+ const positional = rawArgs.filter(
+ (arg, index) =>
+ arg !== "--force-recreate" &&
+ arg !== "--no-build" &&
+ arg !== "--web-only" &&
+ arg !== "--api-only" &&
+ arg !== "--services-only" &&
+ arg !== "--runtime" &&
+ rawArgs[index - 1] !== "--runtime" &&
+ !arg.startsWith("--runtime=")
+ );
+
+ if (forceRecreate && runtime !== "docker") {
+ console.error("--force-recreate is only supported with --runtime docker.");
+ process.exit(1);
+ }
if (positional.length === 1 && positional[0] === "main") {
- return { mode: "main", forceRecreate };
+ return { mode: "main", runtime, scope, forceRecreate, noBuild };
}
if (
(positional.length === 1 && positional[0] === "current-branch") ||
- (positional.length === 2 &&
- positional[0] === "current" &&
- positional[1] === "branch")
+ (positional.length === 2 && positional[0] === "current" && positional[1] === "branch")
) {
- return { mode: "current-branch", forceRecreate };
+ return {
+ mode: "current-branch",
+ runtime,
+ scope,
+ forceRecreate,
+ noBuild
+ };
}
usage();
@@ -162,28 +265,122 @@ function assertSshKeyExists(): void {
}
}
-function localWorkspaceSnapshotPrecheck(): void {
+function shellEscape(value: string): string {
+ if (value.length === 0) {
+ return "''";
+ }
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
+}
+
+function shellPattern(value: string): string {
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
+}
+
+function describeRuntime(runtime: DeployRuntime): string {
+ return runtime === "docker" ? "Docker Compose" : "native systemd/Bun";
+}
+
+function describeScope(scope: DeployScope): string {
+ switch (scope) {
+ case "web":
+ return "web only";
+ case "api":
+ return "api only";
+ case "services":
+ return "api + backend services";
+ default:
+ return "full stack";
+ }
+}
+
+function scopeIncludesWeb(scope: DeployScope): boolean {
+ return scope === "full" || scope === "web";
+}
+
+function scopeIncludesApi(scope: DeployScope): boolean {
+ return scope === "full" || scope === "api" || scope === "services";
+}
+
+function dockerServicesForScope(scope: DeployScope): string[] {
+ switch (scope) {
+ case "web":
+ return ["web"];
+ case "api":
+ return ["api"];
+ case "services":
+ return [...DOCKER_BACKEND_SERVICES];
+ default:
+ return [];
+ }
+}
+
+function dockerLogServicesForScope(scope: DeployScope): string[] {
+ switch (scope) {
+ case "web":
+ return ["web"];
+ case "api":
+ return ["api"];
+ case "services":
+ return [...DOCKER_BACKEND_SERVICES];
+ default:
+ return [...DOCKER_CORE_SERVICES];
+ }
+}
+
+function nativeUnitsForScope(scope: DeployScope): string[] {
+ switch (scope) {
+ case "web":
+ return [NATIVE_UNITS.web];
+ case "api":
+ return [NATIVE_UNITS.api];
+ case "services":
+ return [
+ NATIVE_UNITS.api,
+ NATIVE_UNITS.compute,
+ NATIVE_UNITS.candles,
+ NATIVE_UNITS.ingestOptions,
+ NATIVE_UNITS.ingestEquities
+ ];
+ default:
+ return [
+ NATIVE_UNITS.web,
+ NATIVE_UNITS.api,
+ NATIVE_UNITS.compute,
+ NATIVE_UNITS.candles,
+ NATIVE_UNITS.ingestOptions,
+ NATIVE_UNITS.ingestEquities
+ ];
+ }
+}
+
+function localDockerWorkspaceSnapshotPrecheck(): void {
console.log("$ bun run check:docker-workspace");
const result = spawnSync("bun", ["run", "check:docker-workspace"], {
cwd: repoRoot,
- stdio: "inherit",
+ stdio: "inherit"
});
if (result.status !== 0) {
console.error(
- "Refusing deploy: deployment/docker/workspace-root is out of sync. Run `bun run sync:docker-workspace`, commit updated snapshot files, then retry deploy.",
+ "Refusing docker deploy: deployment/docker/workspace-root is out of sync. Run `bun run sync:docker-workspace`, commit updated snapshot files, then retry deploy."
);
process.exit(result.status ?? 1);
}
}
-function localMainPrecheck(): void {
+function localRuntimePrecheck(runtime: DeployRuntime, noBuild: boolean): void {
+ if (runtime === "docker" && !noBuild) {
+ localDockerWorkspaceSnapshotPrecheck();
+ }
+}
+
+function localMainPrecheck(runtime: DeployRuntime, noBuild: boolean): void {
section("Local Precheck");
runChecked("git", ["fetch", "origin"]);
runChecked("git", ["status", "--short", "--branch"]);
runChecked("git", ["rev-parse", "--verify", "HEAD"]);
runChecked("git", ["rev-parse", "origin/main"]);
- localWorkspaceSnapshotPrecheck();
+ localRuntimePrecheck(runtime, noBuild);
}
function currentBranchName(): string {
@@ -195,7 +392,11 @@ function currentBranchName(): string {
return branch;
}
-function localBranchPrecheck(branch: string): void {
+function localBranchPrecheck(
+ branch: string,
+ runtime: DeployRuntime,
+ noBuild: boolean
+): void {
section("Local Precheck");
runChecked("git", ["branch", "--show-current"]);
runChecked("git", ["status", "--short", "--branch"]);
@@ -204,12 +405,12 @@ function localBranchPrecheck(branch: string): void {
const porcelain = captureChecked("git", ["status", "--porcelain=v1"]).trim();
if (porcelain) {
console.error(
- `Refusing to deploy ${branch} with uncommitted local changes. Commit the intended state first.`,
+ `Refusing to deploy ${branch} with uncommitted local changes. Commit the intended state first.`
);
process.exit(1);
}
- localWorkspaceSnapshotPrecheck();
+ localRuntimePrecheck(runtime, noBuild);
}
function publishCurrentBranch(branch: string): void {
@@ -220,8 +421,8 @@ function publishCurrentBranch(branch: string): void {
{
cwd: repoRoot,
encoding: "utf8",
- stdio: ["inherit", "pipe", "pipe"],
- },
+ stdio: ["inherit", "pipe", "pipe"]
+ }
);
if (upstreamResult.status === 0) {
@@ -232,9 +433,9 @@ function publishCurrentBranch(branch: string): void {
runChecked("git", ["push", "-u", "origin", branch]);
}
-function remotePrecheck(): void {
+function remoteGitPrecheck(): void {
const allowedRemoteUntrackedPattern = Array.from(ALLOWED_REMOTE_UNTRACKED)
- .map((path) => shellPattern(path))
+ .map((value) => shellPattern(value))
.join("|");
runRemoteScript(
@@ -242,7 +443,7 @@ function remotePrecheck(): void {
`#!/usr/bin/env bash
set -euo pipefail
-cd "${REMOTE_REPO}"
+cd ${shellEscape(REMOTE_REPO)}
status="$(git status --porcelain=v1 --branch)"
git status --short --branch
git branch --show-current
@@ -269,104 +470,268 @@ while IFS= read -r line; do
;;
esac
done <<< "$status"
-`,
+`
);
}
-function remoteRollout(
- mode: DeployMode,
- branch: string | null,
- forceRecreate: boolean,
-): void {
- const composeArgs = forceRecreate
- ? "up -d --build --force-recreate"
- : "up -d --build";
+function remoteRuntimePrecheck(runtime: DeployRuntime, scope: DeployScope): void {
+ if (runtime === "docker") {
+ runRemoteScript(
+ "Remote Runtime Precheck",
+ `#!/usr/bin/env bash
+set -euo pipefail
+
+cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)}
+command -v docker >/dev/null 2>&1
+
+docker compose version >/dev/null
+`
+ );
+ return;
+ }
+
+ const units = nativeUnitsForScope(scope).map((value) => shellEscape(value)).join(" ");
+ runRemoteScript(
+ "Remote Runtime Precheck",
+ `#!/usr/bin/env bash
+set -euo pipefail
+
+cd ${shellEscape(REMOTE_REPO)}
+command -v bun >/dev/null 2>&1
+command -v systemctl >/dev/null 2>&1
+
+declare -a units=(${units})
+for unit in "\${units[@]}"; do
+ load_state="$(${NATIVE_SYSTEMCTL_PREFIX} show --property=LoadState --value "$unit" 2>/dev/null || true)"
+ if [[ -z "$load_state" || "$load_state" == "not-found" ]]; then
+ echo "Refusing native rollout: missing systemd unit $unit" >&2
+ echo "See deployment/native/README.md for expected unit names and overrides." >&2
+ exit 1
+ fi
+done
+`
+ );
+}
+
+function remoteGitUpdateScript(mode: DeployMode, branch: string | null): string {
+ const escapedBranch = branch ? shellEscape(branch) : null;
const switchCommand =
mode === "main"
- ? `git switch main
-git pull --ff-only origin main`
- : `git switch ${shellEscape(branch!)} || git switch -c ${shellEscape(branch!)} --track origin/${shellEscape(branch!)}
-git pull --ff-only origin ${shellEscape(branch!)}`;
+ ? `git switch main\ngit pull --ff-only origin main`
+ : `git switch ${escapedBranch} || git switch -c ${escapedBranch} --track origin/${escapedBranch}\ngit pull --ff-only origin ${escapedBranch}`;
+
+ return `cd ${shellEscape(REMOTE_REPO)}\ngit fetch origin\n${switchCommand}`;
+}
+
+function remoteDockerRollout(
+ mode: DeployMode,
+ branch: string | null,
+ scope: DeployScope,
+ forceRecreate: boolean,
+ noBuild: boolean
+): void {
+ const services = dockerServicesForScope(scope);
+ const args = ["up", "-d"];
+ if (!noBuild) {
+ args.push("--build");
+ }
+ if (forceRecreate) {
+ args.push("--force-recreate");
+ }
+ const command = `docker compose ${[...args, ...services].join(" ")}`;
runRemoteScript(
"Remote Rollout",
`#!/usr/bin/env bash
set -euo pipefail
-cd "${REMOTE_REPO}"
-git fetch origin
-${switchCommand}
+${remoteGitUpdateScript(mode, branch)}
-cd "${REMOTE_DEPLOYMENT}"
-docker compose ${composeArgs}
-`,
+cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)}
+${command}
+`
);
}
-function remoteVerification(): void {
+function remoteNativeRollout(
+ mode: DeployMode,
+ branch: string | null,
+ scope: DeployScope,
+ noBuild: boolean
+): void {
+ const units = nativeUnitsForScope(scope).map((value) => shellEscape(value)).join(" ");
+ const buildSteps: string[] = [];
+
+ if (!noBuild) {
+ buildSteps.push("bun install --frozen-lockfile");
+ if (scopeIncludesWeb(scope)) {
+ buildSteps.push("bun --cwd=apps/web run build");
+ }
+ }
+
+ buildSteps.push(`${NATIVE_SYSTEMCTL_PREFIX} restart ${nativeUnitsForScope(scope).map((value) => shellEscape(value)).join(" ")}`);
+
+ runRemoteScript(
+ "Remote Rollout",
+ `#!/usr/bin/env bash
+set -euo pipefail
+
+${remoteGitUpdateScript(mode, branch)}
+
+cd ${shellEscape(REMOTE_REPO)}
+${buildSteps.join("\n")}
+
+declare -a units=(${units})
+for unit in "\${units[@]}"; do
+ ${NATIVE_SYSTEMCTL_PREFIX} is-active --quiet "$unit"
+done
+`
+ );
+}
+
+function remoteRollout(
+ mode: DeployMode,
+ runtime: DeployRuntime,
+ branch: string | null,
+ scope: DeployScope,
+ forceRecreate: boolean,
+ noBuild: boolean
+): void {
+ if (runtime === "docker") {
+ remoteDockerRollout(mode, branch, scope, forceRecreate, noBuild);
+ return;
+ }
+
+ remoteNativeRollout(mode, branch, scope, noBuild);
+}
+
+function remoteDockerVerification(scope: DeployScope): void {
+ const psServices = dockerServicesForScope(scope);
+ const logServices = dockerLogServicesForScope(scope);
+ const psCommand =
+ psServices.length > 0
+ ? `docker compose ps ${psServices.join(" ")}`
+ : "docker compose ps";
+ const logCommand = `docker compose logs --tail=100 ${logServices.join(" ")}`;
+ const checks: string[] = [];
+
+ if (scopeIncludesApi(scope)) {
+ checks.push(
+ `docker compose exec -T api bun -e 'const r = await fetch("http://127.0.0.1:4000/health"); if (!r.ok) throw new Error("api healthcheck failed: " + r.status); console.log(await r.text())'`
+ );
+ }
+
+ if (scopeIncludesWeb(scope)) {
+ checks.push(
+ `docker compose exec -T web bun -e 'const r = await fetch("http://127.0.0.1:3000/"); if (!r.ok) throw new Error("web healthcheck failed: " + r.status); console.log(r.status)'`
+ );
+ }
+
runRemoteScript(
"Remote Verification",
`#!/usr/bin/env bash
set -euo pipefail
-cd "${REMOTE_DEPLOYMENT}"
-docker compose ps
-docker compose logs --tail=100 ${LOG_SERVICES.join(" ")}
-docker exec ${API_CONTAINER} bun -e 'const r = await fetch("http://127.0.0.1:4000/health"); console.log(await r.text())'
-docker exec ${WEB_CONTAINER} bun -e 'const r = await fetch("http://127.0.0.1:3000/"); console.log(r.status)'
-`,
+cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)}
+${psCommand}
+${logCommand}
+${checks.join("\n")}
+`
);
}
-function publicVerification(): void {
+function remoteNativeVerification(scope: DeployScope): void {
+ const units = nativeUnitsForScope(scope).map((value) => shellEscape(value)).join(" ");
+ const checks: string[] = [];
+
+ if (scopeIncludesApi(scope)) {
+ checks.push('curl -fksS http://127.0.0.1:4000/health');
+ }
+
+ if (scopeIncludesWeb(scope)) {
+ checks.push('curl -I -fksS http://127.0.0.1:3000/');
+ }
+
+ runRemoteScript(
+ "Remote Verification",
+ `#!/usr/bin/env bash
+set -euo pipefail
+
+declare -a units=(${units})
+for unit in "\${units[@]}"; do
+ ${NATIVE_SYSTEMCTL_PREFIX} is-active --quiet "$unit"
+ ${NATIVE_SYSTEMCTL_PREFIX} status --no-pager "$unit" || true
+ journalctl -u "$unit" -n 50 --no-pager || true
+done
+${checks.join("\n")}
+`
+ );
+}
+
+function remoteVerification(runtime: DeployRuntime, scope: DeployScope): void {
+ if (runtime === "docker") {
+ remoteDockerVerification(scope);
+ return;
+ }
+
+ remoteNativeVerification(scope);
+}
+
+function publicVerification(scope: DeployScope): void {
section("Public Verification");
runChecked("curl", ["-I", "-fksS", PUBLIC_APP_URL]);
- if (PUBLIC_API_HEALTH_URL) {
+ if (scopeIncludesApi(scope) && PUBLIC_API_HEALTH_URL) {
runChecked("curl", ["-fksS", PUBLIC_API_HEALTH_URL]);
return;
}
- console.log(
- "Skipping separate public API health check; same-origin mode relies on the public app check plus container-local API verification.",
- );
-}
-
-function shellEscape(value: string): string {
- if (value.length === 0) {
- return "''";
+ if (scopeIncludesApi(scope)) {
+ console.log(
+ "Skipping separate public API health check; same-origin mode relies on the public app check plus runtime-local API verification."
+ );
}
- return `'${value.replace(/'/g, `'\"'\"'`)}'`;
-}
-
-function shellPattern(value: string): string {
- return `'${value.replace(/'/g, `'\"'\"'`)}'`;
}
function main(): void {
- const { mode, forceRecreate } = parseArgs(process.argv.slice(2));
+ const options = parseArgs(process.argv.slice(2));
assertSshKeyExists();
console.log(
- mode === "main"
- ? "Deploying origin/main to the existing Islandflow VPS checkout."
- : "Deploying the current local branch to the existing Islandflow VPS checkout.",
+ `Deploying ${options.mode === "main" ? "origin/main" : "the current local branch"} ` +
+ `via ${describeRuntime(options.runtime)} (${describeScope(options.scope)}).`
);
- if (mode === "main") {
- localMainPrecheck();
- remotePrecheck();
- remoteRollout(mode, null, forceRecreate);
+ if (options.mode === "main") {
+ localMainPrecheck(options.runtime, options.noBuild);
+ remoteGitPrecheck();
+ remoteRuntimePrecheck(options.runtime, options.scope);
+ remoteRollout(
+ options.mode,
+ options.runtime,
+ null,
+ options.scope,
+ options.forceRecreate,
+ options.noBuild
+ );
} else {
const branch = currentBranchName();
- localBranchPrecheck(branch);
+ localBranchPrecheck(branch, options.runtime, options.noBuild);
publishCurrentBranch(branch);
- remotePrecheck();
- remoteRollout(mode, branch, forceRecreate);
+ remoteGitPrecheck();
+ remoteRuntimePrecheck(options.runtime, options.scope);
+ remoteRollout(
+ options.mode,
+ options.runtime,
+ branch,
+ options.scope,
+ options.forceRecreate,
+ options.noBuild
+ );
}
- remoteVerification();
- publicVerification();
+ remoteVerification(options.runtime, options.scope);
+ publicVerification(options.scope);
}
main();
From d7e984ce546f7ed8d8a99867f0b49fb5b96b0435 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 15 May 2026 20:52:46 -0400
Subject: [PATCH 014/107] Fix deploy wrapper argument forwarding
---
.beads/issues.jsonl | 1 +
deploy | 2 +-
docs/turns/2026-05-15-dual-runtime-deploy-workflow.html | 2 ++
3 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index d601adc..32f80d5 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -10,6 +10,7 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-7p2","title":"Fix deploy wrapper argument forwarding for runtime flags","description":"The repo-root deploy wrapper currently invokes bun run without a -- separator, so flags like --runtime native are treated as Bun CLI flags instead of script arguments. Update the wrapper so ./deploy main --runtime native forwards arguments correctly to scripts/deploy.ts.","notes":"Cherry-picked the dual-runtime deploy workflow onto main and fixed the repo-root deploy wrapper to call Bun with a -- separator so flags like --runtime native are forwarded to scripts/deploy.ts correctly. Validation: ./deploy --help, ./deploy main --runtime native --force-recreate guard, bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T00:51:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T00:52:34Z","started_at":"2026-05-16T00:51:10Z","closed_at":"2026-05-16T00:52:34Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-qh7","title":"Implement dual-runtime deploy workflow with partial deploys","description":"Implement the planned refactor of the root deploy script and scripts/deploy.ts so deployment can target Docker and host-native runtimes during a transition period. Preserve local dev as Docker infra plus native Bun services/web, add explicit runtime selection, runtime-specific prechecks/rollout/verification, and support partial deploy scopes such as web-only or services-only. Update operator documentation for the new workflow.","notes":"Implemented dual-runtime deploy workflow. scripts/deploy.ts now supports --runtime docker|native, scope flags (--web-only, --api-only, --services-only), and --no-build. Docker verification now uses docker compose exec instead of hardcoded container names. Added deployment/native/README.md and updated README.md plus deployment/docker/README.md for the new workflow. Validation: bun run scripts/deploy.ts --help, bun run check:docker-workspace, guard checks for invalid flag combinations.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:38:31Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:17Z","started_at":"2026-05-15T23:40:13Z","closed_at":"2026-05-15T23:46:17Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-iiy","title":"Plan deploy workflow changes for Docker/native transition","description":"User requested a repo-specific plan for updating the root deploy script and deployment workflow to support Docker/native transition paths, faster local iteration, and partial deploy modes. This task covers confirming the target workflow, documenting current assumptions, and producing an implementation-ready plan without changing implementation files.","notes":"Confirmed transition strategy: local dev stays Docker-infra-only plus native Bun services/web; VPS deploy path should support both Docker and host-native runtimes during transition; partial deploys are desired; current main/current-branch modes may evolve. Produced an implementation-ready plan covering current assumptions, runtime split, CLI shape, prechecks, rollout, verification, rollback, docs, and validation scenarios. Follow-up implementation tracked in islandflow-qh7.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:37:28Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:38:41Z","started_at":"2026-05-15T23:37:30Z","closed_at":"2026-05-15T23:38:41Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-wab","title":"Quiet the terminal view chrome","description":"The Islandflow terminal view currently carries too much chrome intensity: strong shell gradients, visible grid texture, active amber wash, glassy overlays, and heavily styled drawer/filter surfaces compete with live data. Refine the product UI so the terminal feels calmer and more forensic while preserving status clarity, scan speed, and identity. Focus on reducing decorative contrast, flattening surfaces, and making accents scarcer without weakening affordances.","notes":"Refined terminal chrome in apps/web/app/globals.css: moved shell tokens to quieter OKLCH values, removed grid texture, flattened panes/overlays, reduced active amber wash, softened classified row treatment, and added reduced-motion handling for the connecting pulse. Validation: bun test apps/web/app/terminal.test.ts; bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T12:05:25Z","created_by":"dirtydishes","updated_at":"2026-05-15T12:13:10Z","started_at":"2026-05-15T12:05:30Z","closed_at":"2026-05-15T12:13:10Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/deploy b/deploy
index 0da6ddc..cf170a4 100755
--- a/deploy
+++ b/deploy
@@ -2,4 +2,4 @@
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-exec bun run "$repo_root/scripts/deploy.ts" "$@"
+exec bun run "$repo_root/scripts/deploy.ts" -- "$@"
diff --git a/docs/turns/2026-05-15-dual-runtime-deploy-workflow.html b/docs/turns/2026-05-15-dual-runtime-deploy-workflow.html
index 7fe2a42..b0bf02a 100644
--- a/docs/turns/2026-05-15-dual-runtime-deploy-workflow.html
+++ b/docs/turns/2026-05-15-dual-runtime-deploy-workflow.html
@@ -101,6 +101,7 @@
Changes Made
Refactored scripts/deploy.ts into shared git/publish logic plus runtime-specific precheck, rollout, and verification paths.
+
Fixed the repo-root deploy wrapper to forward flags such as --runtime native through Bun correctly.
Removed Docker verification’s dependence on hardcoded container names and switched to docker compose exec.
Added native deployment support that assumes Bun plus systemd-managed units on the VPS.
Added a new operator guide at deployment/native/README.md.
@@ -137,6 +138,7 @@
Validation
+
Passed: ./deploy --help
Passed: bun run scripts/deploy.ts --help
Passed: bun run check:docker-workspace
Passed: invalid-flag guard for --runtime native --force-recreate
From 958c8afeede45901212e831c4d7defdc2a35d351 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 15 May 2026 21:13:02 -0400
Subject: [PATCH 015/107] Clarify Docker-first deploy workflow
---
.beads/issues.jsonl | 1 +
README.md | 8 +-
deployment/docker/README.md | 6 +-
deployment/native/README.md | 25 ++-
...-clarify-docker-first-deploy-workflow.html | 146 ++++++++++++++++++
scripts/deploy.ts | 44 +++++-
6 files changed, 213 insertions(+), 17 deletions(-)
create mode 100644 docs/turns/2026-05-15-clarify-docker-first-deploy-workflow.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 32f80d5..2edb51c 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -10,6 +10,7 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-4gj","title":"Clarify Docker-first deploy workflow and mark native runtime experimental","description":"After inspecting the live VPS, native deployment is not ready for routine use: Nginx Proxy Manager routes to Docker container names, Bun is not installed on the host, sudo systemctl is not passwordless, and no Islandflow units exist. Update deploy messaging and docs so Docker remains the clearly recommended deployment path and native runtime is labeled experimental/future-facing with server prerequisites called out.","notes":"Updated deploy messaging and docs after live VPS inspection. scripts/deploy.ts now marks Docker as the default and recommended runtime, labels native as experimental, switches native systemctl default to sudo -n systemctl, and prints explicit native precheck failures for missing Bun/systemctl access/units. Updated README.md, deployment/docker/README.md, and deployment/native/README.md to reflect the current Docker + Nginx Proxy Manager topology. Validation: ./deploy --help, ./deploy main --runtime native --no-build (fails fast with Bun-missing message), bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:10:11Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:12:39Z","started_at":"2026-05-16T01:10:14Z","closed_at":"2026-05-16T01:12:39Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-7p2","title":"Fix deploy wrapper argument forwarding for runtime flags","description":"The repo-root deploy wrapper currently invokes bun run without a -- separator, so flags like --runtime native are treated as Bun CLI flags instead of script arguments. Update the wrapper so ./deploy main --runtime native forwards arguments correctly to scripts/deploy.ts.","notes":"Cherry-picked the dual-runtime deploy workflow onto main and fixed the repo-root deploy wrapper to call Bun with a -- separator so flags like --runtime native are forwarded to scripts/deploy.ts correctly. Validation: ./deploy --help, ./deploy main --runtime native --force-recreate guard, bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T00:51:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T00:52:34Z","started_at":"2026-05-16T00:51:10Z","closed_at":"2026-05-16T00:52:34Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-qh7","title":"Implement dual-runtime deploy workflow with partial deploys","description":"Implement the planned refactor of the root deploy script and scripts/deploy.ts so deployment can target Docker and host-native runtimes during a transition period. Preserve local dev as Docker infra plus native Bun services/web, add explicit runtime selection, runtime-specific prechecks/rollout/verification, and support partial deploy scopes such as web-only or services-only. Update operator documentation for the new workflow.","notes":"Implemented dual-runtime deploy workflow. scripts/deploy.ts now supports --runtime docker|native, scope flags (--web-only, --api-only, --services-only), and --no-build. Docker verification now uses docker compose exec instead of hardcoded container names. Added deployment/native/README.md and updated README.md plus deployment/docker/README.md for the new workflow. Validation: bun run scripts/deploy.ts --help, bun run check:docker-workspace, guard checks for invalid flag combinations.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:38:31Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:17Z","started_at":"2026-05-15T23:40:13Z","closed_at":"2026-05-15T23:46:17Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-iiy","title":"Plan deploy workflow changes for Docker/native transition","description":"User requested a repo-specific plan for updating the root deploy script and deployment workflow to support Docker/native transition paths, faster local iteration, and partial deploy modes. This task covers confirming the target workflow, documenting current assumptions, and producing an implementation-ready plan without changing implementation files.","notes":"Confirmed transition strategy: local dev stays Docker-infra-only plus native Bun services/web; VPS deploy path should support both Docker and host-native runtimes during transition; partial deploys are desired; current main/current-branch modes may evolve. Produced an implementation-ready plan covering current assumptions, runtime split, CLI shape, prechecks, rollout, verification, rollback, docs, and validation scenarios. Follow-up implementation tracked in islandflow-qh7.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:37:28Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:38:41Z","started_at":"2026-05-15T23:37:30Z","closed_at":"2026-05-15T23:38:41Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/README.md b/README.md
index 3542353..f6d0085 100644
--- a/README.md
+++ b/README.md
@@ -126,12 +126,12 @@ This keeps Docker in the local workflow where it helps most (NATS, ClickHouse, R
## Deployment Workflow
-- `./deploy main` keeps the current VPS Docker rollout path as the default.
-- `./deploy main --runtime native` targets a host-native Bun + systemd deployment.
-- `./deploy current-branch` and `./deploy current-branch --runtime native` keep branch deploys available during the transition.
+- `./deploy main` keeps the current VPS Docker rollout path as the default and recommended path.
+- `./deploy main --runtime native` targets an experimental host-native Bun + systemd deployment.
+- `./deploy current-branch` and `./deploy current-branch --runtime native` keep branch deploys available during the transition, but Docker remains the supported path for the current VPS.
- Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, and `--no-build`.
- Docker runtime details live in `deployment/docker/README.md`.
-- Native runtime expectations live in `deployment/native/README.md`.
+- Native runtime expectations and prerequisites live in `deployment/native/README.md`.
## Desktop Shell
diff --git a/deployment/docker/README.md b/deployment/docker/README.md
index 426a006..a6cc1d5 100644
--- a/deployment/docker/README.md
+++ b/deployment/docker/README.md
@@ -2,10 +2,10 @@
This directory contains the Docker runtime for Islandflow VPS deployments.
-Docker remains the default server rollout path, but the repo-root `deploy` helper can now target either:
+Docker remains the default and recommended server rollout path, but the repo-root `deploy` helper can now target either:
- `--runtime docker` for this Docker Compose stack
-- `--runtime native` for a host-native Bun + systemd rollout described in `deployment/native/README.md`
+- `--runtime native` for an experimental host-native Bun + systemd rollout described in `deployment/native/README.md`
The repo no longer ships or supports a separate `deployment/npm` stack. If you want a reverse proxy, point it at the host ports published by this stack.
@@ -190,6 +190,8 @@ docker compose build web
## Safe rollouts on `152.53.80.229`
+The current live VPS uses Nginx Proxy Manager on the shared Docker network and routes public traffic to the Docker `web` and `api` containers by container name. Because of that, this Docker path remains the operationally correct default for the live server today.
+
The checked-in deploy helper is meant to run from your local repo checkout, not from the VPS shell. It always targets:
- SSH host: `delta@152.53.80.229`
diff --git a/deployment/native/README.md b/deployment/native/README.md
index fed5b74..03c5bf7 100644
--- a/deployment/native/README.md
+++ b/deployment/native/README.md
@@ -1,13 +1,13 @@
# Native Deployment
-This directory documents the host-native Islandflow rollout path used by:
+This directory documents the experimental host-native Islandflow rollout path used by:
```bash
./deploy main --runtime native
./deploy current-branch --runtime native
```
-This runtime is intended for faster server iteration during the transition away from Docker-only app rollouts. Local development should still prefer:
+This runtime is intended for faster server iteration during the transition away from Docker-only app rollouts. It is not the recommended path for the current production VPS, which still uses Nginx Proxy Manager to reach the Docker `web` and `api` containers by container name on the shared Docker network. Local development should still prefer:
- Docker for infra (`bun run dev:infra`)
- native Bun services (`bun run dev:services`)
@@ -57,7 +57,7 @@ Available overrides:
By default the deploy helper uses:
```bash
-sudo systemctl
+sudo -n systemctl
```
If the server uses user units or another wrapper, override it locally before invoking `./deploy`:
@@ -86,6 +86,23 @@ Scope behavior:
- `--services-only`: restart API + backend units without touching the web unit
- `--no-build`: skip `bun install --frozen-lockfile` and skip the web build step
+## Current status
+
+On the current live VPS, native deploys should be treated as opt-in infrastructure work, not the default rollout path. Before a native deploy can succeed there, all of the following must be true at the same time:
+
+- Bun is installed on the host.
+- The selected `systemctl` command works non-interactively.
+- Islandflow systemd units exist for the requested scope.
+- Host-native services can reach the intended NATS, ClickHouse, and Redis endpoints.
+- If `web` or `api` move native, the reverse proxy topology is updated deliberately.
+
+Until that is prepared intentionally, prefer:
+
+```bash
+./deploy main --runtime docker
+./deploy current-branch --runtime docker
+```
+
## Server preparation checklist
Before the first native rollout, ensure the VPS has:
@@ -115,7 +132,7 @@ Rollback remains manual for now:
2. rerun the appropriate native deploy command
3. if needed, restart only the affected units with `systemctl`
-Docker remains available as the fallback runtime during the transition:
+Docker remains the fallback and currently recommended runtime during the transition:
```bash
./deploy main --runtime docker
diff --git a/docs/turns/2026-05-15-clarify-docker-first-deploy-workflow.html b/docs/turns/2026-05-15-clarify-docker-first-deploy-workflow.html
new file mode 100644
index 0000000..7f40e58
--- /dev/null
+++ b/docs/turns/2026-05-15-clarify-docker-first-deploy-workflow.html
@@ -0,0 +1,146 @@
+
+
+
+
+
+ 2026-05-15: Clarify Docker-first deploy workflow
+
+
+
+
+
+ Updated deploy messaging and deployment docs so Docker is clearly the supported VPS path today, while the native runtime is labeled experimental and fails faster with clearer prerequisites.
+
+
+
+
Summary
+
+ The deploy helper now warns when --runtime native is used, defaults native systemctl invocations to sudo -n systemctl so they fail fast instead of hanging for a password, and prints explicit precheck errors when Bun or systemd readiness is missing. Docs now describe Docker as the default and recommended VPS rollout path.
+
+
+
+
+
Changes Made
+
+
Updated scripts/deploy.ts help text to mark Docker as default and recommended, and native as experimental.
+
Changed the native systemctl default from sudo systemctl to sudo -n systemctl to avoid interactive hangs.
+
Added a runtime advisory banner for native deploy attempts.
+
Improved native remote precheck failures for missing Bun, missing systemctl access, and missing systemd units.
+
Updated README.md, deployment/docker/README.md, and deployment/native/README.md to reflect the live VPS reality: Docker plus Nginx Proxy Manager remains the supported deployment path.
+
+
+
+
+
Context
+
+ Live inspection of the VPS showed that Nginx Proxy Manager routes flow.deltaisland.io and API traffic to the Docker web and api containers by container name on the shared Docker network. The host does not currently have Bun installed, passwordless sudo systemctl is not configured, and no Islandflow systemd units are present. Because of that, native deployment should be treated as future infrastructure work rather than the recommended day-to-day path.
+
+
+
+
+
Important Implementation Details
+
+
Native rollout prechecks now fail with actionable messages instead of a silent command failure or a hanging sudo prompt.
+
The native docs now explicitly say the current VPS is not prepared for routine native rollouts.
+
Docker deployment behavior itself was not changed. This was a clarity and guardrail pass, not a runtime migration.
+
+
[deploy] Native runtime is experimental. Use --runtime docker for the current supported VPS path unless Bun, systemd units, and proxy routing have been prepared intentionally.
+
+
+
+
Validation
+
+
Passed: ./deploy --help
+
Passed: bun run check:docker-workspace
+
Passed: ./deploy main --runtime native --no-build now fails quickly with an explicit Bun-missing message on the live VPS
+
+
./deploy --help
+./deploy main --runtime native --no-build
+bun run check:docker-workspace
+
+
+
+
Issues, Limitations, and Mitigations
+
+
Native deploy remains experimental. Mitigation: docs and CLI output now say so directly.
+
The live VPS still depends on Docker-name routing through Nginx Proxy Manager. Mitigation: Docker remains the recommended deployment path.
+
No systemd units or Bun install were added in this change. That work remains a separate follow-up.
+
+
+
+
+
Follow-up Work
+
+
Keep native deployment support available for future experimentation, but treat it as opt-in infrastructure work.
+
Open follow-up: islandflow-38p, add native deployment unit templates and rollback helpers if the host-native path is revived later.
+
+
+
+
+
diff --git a/scripts/deploy.ts b/scripts/deploy.ts
index f56598d..183f833 100644
--- a/scripts/deploy.ts
+++ b/scripts/deploy.ts
@@ -38,7 +38,7 @@ const PUBLIC_APP_URL =
const PUBLIC_API_HEALTH_URL =
process.env.DEPLOY_PUBLIC_API_HEALTH_URL?.trim() || null;
const NATIVE_SYSTEMCTL_PREFIX =
- process.env.DEPLOY_NATIVE_SYSTEMCTL_PREFIX?.trim() || "sudo systemctl";
+ process.env.DEPLOY_NATIVE_SYSTEMCTL_PREFIX?.trim() || "sudo -n systemctl";
const NATIVE_UNITS = {
web: process.env.DEPLOY_NATIVE_WEB_UNIT?.trim() || "islandflow-web",
api: process.env.DEPLOY_NATIVE_API_UNIT?.trim() || "islandflow-api",
@@ -79,8 +79,8 @@ Modes:
current-branch Push the current local branch, switch the server to it, and deploy it.
Runtimes:
- docker Roll out from deployment/docker with Docker Compose (default).
- native Roll out host-native Bun services managed by systemd.
+ docker Roll out from deployment/docker with Docker Compose (default, recommended).
+ native Experimental host-native Bun services managed by systemd.
Scopes:
default Full rollout (web + API + backend services).
@@ -97,7 +97,7 @@ Options:
Environment:
DEPLOY_PUBLIC_APP_URL Override the public app URL (default: https://flow.deltaisland.io).
DEPLOY_PUBLIC_API_HEALTH_URL Optional separate public API health URL for two-origin deployments.
- DEPLOY_NATIVE_SYSTEMCTL_PREFIX Override systemctl invocation for native rollouts (default: sudo systemctl).
+ DEPLOY_NATIVE_SYSTEMCTL_PREFIX Override systemctl invocation for native rollouts (default: sudo -n systemctl).
DEPLOY_NATIVE_WEB_UNIT Override native web systemd unit name.
DEPLOY_NATIVE_API_UNIT Override native api systemd unit name.
DEPLOY_NATIVE_COMPUTE_UNIT Override native compute systemd unit name.
@@ -277,7 +277,17 @@ function shellPattern(value: string): string {
}
function describeRuntime(runtime: DeployRuntime): string {
- return runtime === "docker" ? "Docker Compose" : "native systemd/Bun";
+ return runtime === "docker" ? "Docker Compose" : "experimental native systemd/Bun";
+}
+
+function printRuntimeAdvisory(runtime: DeployRuntime): void {
+ if (runtime !== "native") {
+ return;
+ }
+
+ console.warn(
+ "[deploy] Native runtime is experimental. Use --runtime docker for the current supported VPS path unless Bun, systemd units, and proxy routing have been prepared intentionally."
+ );
}
function describeScope(scope: DeployScope): string {
@@ -497,8 +507,26 @@ docker compose version >/dev/null
set -euo pipefail
cd ${shellEscape(REMOTE_REPO)}
-command -v bun >/dev/null 2>&1
-command -v systemctl >/dev/null 2>&1
+
+if ! command -v bun >/dev/null 2>&1; then
+ echo "Refusing native rollout: bun is not installed on the server." >&2
+ echo "The current supported VPS path remains --runtime docker." >&2
+ echo "See deployment/native/README.md for native prerequisites." >&2
+ exit 1
+fi
+
+if ! command -v systemctl >/dev/null 2>&1; then
+ echo "Refusing native rollout: systemctl is not available on the server." >&2
+ echo "See deployment/native/README.md for native prerequisites." >&2
+ exit 1
+fi
+
+if ! ${NATIVE_SYSTEMCTL_PREFIX} --version >/dev/null 2>&1; then
+ echo "Refusing native rollout: cannot run ${NATIVE_SYSTEMCTL_PREFIX}." >&2
+ echo "If the server uses user units, try DEPLOY_NATIVE_SYSTEMCTL_PREFIX='systemctl --user'." >&2
+ echo "If the server uses system units, ensure passwordless sudo for this command or use --runtime docker." >&2
+ exit 1
+fi
declare -a units=(${units})
for unit in "\${units[@]}"; do
@@ -506,6 +534,7 @@ for unit in "\${units[@]}"; do
if [[ -z "$load_state" || "$load_state" == "not-found" ]]; then
echo "Refusing native rollout: missing systemd unit $unit" >&2
echo "See deployment/native/README.md for expected unit names and overrides." >&2
+ echo "Use --runtime docker for the current supported VPS path." >&2
exit 1
fi
done
@@ -696,6 +725,7 @@ function publicVerification(scope: DeployScope): void {
function main(): void {
const options = parseArgs(process.argv.slice(2));
assertSshKeyExists();
+ printRuntimeAdvisory(options.runtime);
console.log(
`Deploying ${options.mode === "main" ? "origin/main" : "the current local branch"} ` +
From 7caef80718c910ac63e99939ef5d8c928fcacf5c Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 15 May 2026 21:29:38 -0400
Subject: [PATCH 016/107] Warn about duplicate VPS compose stacks
---
.beads/issues.jsonl | 2 +
README.md | 1 +
deployment/docker/README.md | 4 +
...-15-add-duplicate-vps-compose-warning.html | 116 ++++++++++++++++++
scripts/deploy.ts | 6 +
5 files changed, 129 insertions(+)
create mode 100644 docs/turns/2026-05-15-add-duplicate-vps-compose-warning.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 2edb51c..bbea524 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -10,6 +10,8 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-c87","title":"Clean up duplicate Islandflow Docker infra on VPS","description":"The live VPS is currently running both the production-style islandflow-vps Docker stack and an older root-level islandflow infra stack that publishes NATS, ClickHouse, and Redis on host ports. Investigate whether the older stack is unused, remove it safely if so, and update docs/deploy guidance so the server topology is clearer.","notes":"Inspected the live VPS and confirmed the duplicate compose project: islandflow-vps is the supported deployment stack, while a separate islandflow project from the repo-root docker-compose.yml still runs exposed NATS/ClickHouse/Redis containers. Verified Nginx Proxy Manager routes only to islandflow-vps web/api by Docker name. Attempted cleanup via docker compose down and docker rm -f on the stale islandflow containers, but those commands hung for the delta user and timed out. Added repo guardrails and docs so deploy warns when the duplicate project exists, and opened islandflow-2db for manual host-level cleanup during a maintenance window.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:16:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:07Z","started_at":"2026-05-16T01:16:09Z","closed_at":"2026-05-16T01:28:07Z","close_reason":"Completed the repo-side investigation and guardrails. Actual server-side container removal is blocked by hanging Docker operations and is tracked separately in islandflow-2db for a maintenance window.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-4gj","title":"Clarify Docker-first deploy workflow and mark native runtime experimental","description":"After inspecting the live VPS, native deployment is not ready for routine use: Nginx Proxy Manager routes to Docker container names, Bun is not installed on the host, sudo systemctl is not passwordless, and no Islandflow units exist. Update deploy messaging and docs so Docker remains the clearly recommended deployment path and native runtime is labeled experimental/future-facing with server prerequisites called out.","notes":"Updated deploy messaging and docs after live VPS inspection. scripts/deploy.ts now marks Docker as the default and recommended runtime, labels native as experimental, switches native systemctl default to sudo -n systemctl, and prints explicit native precheck failures for missing Bun/systemctl access/units. Updated README.md, deployment/docker/README.md, and deployment/native/README.md to reflect the current Docker + Nginx Proxy Manager topology. Validation: ./deploy --help, ./deploy main --runtime native --no-build (fails fast with Bun-missing message), bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:10:11Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:12:39Z","started_at":"2026-05-16T01:10:14Z","closed_at":"2026-05-16T01:12:39Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-7p2","title":"Fix deploy wrapper argument forwarding for runtime flags","description":"The repo-root deploy wrapper currently invokes bun run without a -- separator, so flags like --runtime native are treated as Bun CLI flags instead of script arguments. Update the wrapper so ./deploy main --runtime native forwards arguments correctly to scripts/deploy.ts.","notes":"Cherry-picked the dual-runtime deploy workflow onto main and fixed the repo-root deploy wrapper to call Bun with a -- separator so flags like --runtime native are forwarded to scripts/deploy.ts correctly. Validation: ./deploy --help, ./deploy main --runtime native --force-recreate guard, bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T00:51:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T00:52:34Z","started_at":"2026-05-16T00:51:10Z","closed_at":"2026-05-16T00:52:34Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-qh7","title":"Implement dual-runtime deploy workflow with partial deploys","description":"Implement the planned refactor of the root deploy script and scripts/deploy.ts so deployment can target Docker and host-native runtimes during a transition period. Preserve local dev as Docker infra plus native Bun services/web, add explicit runtime selection, runtime-specific prechecks/rollout/verification, and support partial deploy scopes such as web-only or services-only. Update operator documentation for the new workflow.","notes":"Implemented dual-runtime deploy workflow. scripts/deploy.ts now supports --runtime docker|native, scope flags (--web-only, --api-only, --services-only), and --no-build. Docker verification now uses docker compose exec instead of hardcoded container names. Added deployment/native/README.md and updated README.md plus deployment/docker/README.md for the new workflow. Validation: bun run scripts/deploy.ts --help, bun run check:docker-workspace, guard checks for invalid flag combinations.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:38:31Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:17Z","started_at":"2026-05-15T23:40:13Z","closed_at":"2026-05-15T23:46:17Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/README.md b/README.md
index f6d0085..50063d9 100644
--- a/README.md
+++ b/README.md
@@ -127,6 +127,7 @@ This keeps Docker in the local workflow where it helps most (NATS, ClickHouse, R
## Deployment Workflow
- `./deploy main` keeps the current VPS Docker rollout path as the default and recommended path.
+- Do not run the repo-root `docker-compose.yml` on the VPS. That file is for local infra only and can create duplicate exposed NATS, ClickHouse, and Redis containers on the server.
- `./deploy main --runtime native` targets an experimental host-native Bun + systemd deployment.
- `./deploy current-branch` and `./deploy current-branch --runtime native` keep branch deploys available during the transition, but Docker remains the supported path for the current VPS.
- Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, and `--no-build`.
diff --git a/deployment/docker/README.md b/deployment/docker/README.md
index a6cc1d5..7c4f03b 100644
--- a/deployment/docker/README.md
+++ b/deployment/docker/README.md
@@ -11,6 +11,8 @@ The repo no longer ships or supports a separate `deployment/npm` stack. If you w
It is separate from the repo-root `docker-compose.yml`, which remains the lightweight local infra stack for development.
+Do not run the repo-root `docker-compose.yml` on the VPS. On the live server that creates a second compose project with host-published NATS, ClickHouse, and Redis ports that are not part of the supported production topology.
+
## What this stack does
- Builds and runs the full Islandflow stack with Docker Compose.
@@ -192,6 +194,8 @@ docker compose build web
The current live VPS uses Nginx Proxy Manager on the shared Docker network and routes public traffic to the Docker `web` and `api` containers by container name. Because of that, this Docker path remains the operationally correct default for the live server today.
+The deploy helper also warns if it detects a second compose project named `islandflow` on the server, because that usually means the repo-root local-infra stack was started on the VPS by mistake.
+
The checked-in deploy helper is meant to run from your local repo checkout, not from the VPS shell. It always targets:
- SSH host: `delta@152.53.80.229`
diff --git a/docs/turns/2026-05-15-add-duplicate-vps-compose-warning.html b/docs/turns/2026-05-15-add-duplicate-vps-compose-warning.html
new file mode 100644
index 0000000..c9a2ffe
--- /dev/null
+++ b/docs/turns/2026-05-15-add-duplicate-vps-compose-warning.html
@@ -0,0 +1,116 @@
+
+
+
+
+
+ 2026-05-15: Warn about duplicate VPS compose stacks
+
+
+
+
+
+ Investigated the live VPS, confirmed that a second compose project from the repo-root local-infra stack is still running there, attempted cleanup, and added deploy/docs guardrails so this state is easier to spot and less likely to be recreated accidentally.
+
+
+
+
Summary
+
+ The live server currently has both islandflow-vps and an older islandflow compose project. The supported production traffic path uses islandflow-vps. I added a deploy-time warning and documentation updates so Docker remains the intended VPS path and the repo-root docker-compose.yml is clearly marked as local-only. I also attempted to remove the stale islandflow containers on the VPS, but Docker operations against them hung and timed out, so manual cleanup is tracked separately.
+
+
+
+
+
Changes Made
+
+
Updated scripts/deploy.ts so Docker runtime prechecks warn when the server also has a compose project named islandflow.
+
Updated README.md to explicitly say the repo-root docker-compose.yml is for local infra only and should not be run on the VPS.
+
Updated deployment/docker/README.md with the same warning and a note about the duplicate-project detector.
+
Inspected the live VPS and confirmed that Nginx Proxy Manager routes public traffic to islandflow-vps container names on the shared Docker network.
+
Attempted docker compose down and forced container removal for the stale islandflow project, but those operations timed out when run as the normal deploy user.
+
+
+
+
+
Context
+
+ The repo has two Docker entry points with different purposes. The root docker-compose.yml is a local development infra stack that publishes NATS, ClickHouse, and Redis on host ports. The supported VPS deployment lives under deployment/docker/ and uses an islandflow-vps compose project, internal service-name routing, and Nginx Proxy Manager on the shared Docker network. Running both on the VPS creates duplicate infra and can make host-level debugging confusing.
+
+
+
+
+
Important Implementation Details
+
+
The new warning is advisory only. It does not block Docker deploys, because the current live server still has the duplicate project and production deploys must keep working.
+
The detection looks for containers whose compose project label is islandflow, which matches the repo-root stack on the VPS.
+
The stale containers are still present as of this turn. Removal is blocked by hanging Docker operations and likely needs a maintenance window or host-level intervention.
+
+
[deploy] Warning: found an additional compose project named "islandflow" on the server.
+[deploy] The live VPS should normally use only the deployment/docker stack (compose project "islandflow-vps").
+[deploy] The repo-root docker-compose.yml is for local infra and can create duplicate exposed NATS, ClickHouse, and Redis services on the VPS.
+
+
+
+
Validation
+
+
Passed: ./deploy --help
+
Passed: bun run check:docker-workspace
+
Passed: live VPS inspection confirming duplicate compose containers still exist
+
Passed: public app health check after the cleanup attempt, https://flow.deltaisland.io still returned HTTP 200
The stale VPS containers were not removed in this turn. Mitigation: tracked manual cleanup in islandflow-2db.
+
Docker commands targeting those old containers hung and timed out. Mitigation: avoided risky broader actions that could impact the live islandflow-vps stack.
+
The deploy warning is non-blocking. That is intentional so normal Docker deploys continue to work while the duplicate stack is still present.
+
+
+
+
+
Follow-up Work
+
+
Open follow-up: islandflow-2db, manually remove the stale islandflow local-infra containers from the VPS during a maintenance window.
+
+
+
+
+
diff --git a/scripts/deploy.ts b/scripts/deploy.ts
index 183f833..1ec3e6c 100644
--- a/scripts/deploy.ts
+++ b/scripts/deploy.ts
@@ -495,6 +495,12 @@ cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)}
command -v docker >/dev/null 2>&1
docker compose version >/dev/null
+
+if docker ps --format '{{.Names}} {{.Label "com.docker.compose.project"}}' | grep -q '^islandflow-.* islandflow$'; then
+ echo '[deploy] Warning: found an additional compose project named "islandflow" on the server.' >&2
+ echo '[deploy] The live VPS should normally use only the deployment/docker stack (compose project "islandflow-vps").' >&2
+ echo '[deploy] The repo-root docker-compose.yml is for local infra and can create duplicate exposed NATS, ClickHouse, and Redis services on the VPS.' >&2
+fi
`
);
return;
From eaddf4b7a0e995ca9758579b88e10aa3d33afc4f Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 16 May 2026 14:14:56 -0400
Subject: [PATCH 017/107] Update AGENTS.md
---
.beads/issues.jsonl | 1 +
AGENTS.md | 1 +
2 files changed, 2 insertions(+)
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index bbea524..d2acc2b 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -10,6 +10,7 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","status":"in_progress","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:12:54Z","started_at":"2026-05-16T18:12:54Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-c87","title":"Clean up duplicate Islandflow Docker infra on VPS","description":"The live VPS is currently running both the production-style islandflow-vps Docker stack and an older root-level islandflow infra stack that publishes NATS, ClickHouse, and Redis on host ports. Investigate whether the older stack is unused, remove it safely if so, and update docs/deploy guidance so the server topology is clearer.","notes":"Inspected the live VPS and confirmed the duplicate compose project: islandflow-vps is the supported deployment stack, while a separate islandflow project from the repo-root docker-compose.yml still runs exposed NATS/ClickHouse/Redis containers. Verified Nginx Proxy Manager routes only to islandflow-vps web/api by Docker name. Attempted cleanup via docker compose down and docker rm -f on the stale islandflow containers, but those commands hung for the delta user and timed out. Added repo guardrails and docs so deploy warns when the duplicate project exists, and opened islandflow-2db for manual host-level cleanup during a maintenance window.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:16:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:07Z","started_at":"2026-05-16T01:16:09Z","closed_at":"2026-05-16T01:28:07Z","close_reason":"Completed the repo-side investigation and guardrails. Actual server-side container removal is blocked by hanging Docker operations and is tracked separately in islandflow-2db for a maintenance window.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-4gj","title":"Clarify Docker-first deploy workflow and mark native runtime experimental","description":"After inspecting the live VPS, native deployment is not ready for routine use: Nginx Proxy Manager routes to Docker container names, Bun is not installed on the host, sudo systemctl is not passwordless, and no Islandflow units exist. Update deploy messaging and docs so Docker remains the clearly recommended deployment path and native runtime is labeled experimental/future-facing with server prerequisites called out.","notes":"Updated deploy messaging and docs after live VPS inspection. scripts/deploy.ts now marks Docker as the default and recommended runtime, labels native as experimental, switches native systemctl default to sudo -n systemctl, and prints explicit native precheck failures for missing Bun/systemctl access/units. Updated README.md, deployment/docker/README.md, and deployment/native/README.md to reflect the current Docker + Nginx Proxy Manager topology. Validation: ./deploy --help, ./deploy main --runtime native --no-build (fails fast with Bun-missing message), bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:10:11Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:12:39Z","started_at":"2026-05-16T01:10:14Z","closed_at":"2026-05-16T01:12:39Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/AGENTS.md b/AGENTS.md
index 351b68c..b5c7b69 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -117,6 +117,7 @@ Each turn document must include these sections:
2. **Changes Made**
3. **Context**
4. **Important Implementation Details**
+5. **Impact for End-Users**
5. **Validation**
6. **Issues, Limitations, and Mitigations**
7. **Follow-up Work**
From 39fb5ce9f104ba59e249aefedc24b9e711168e03 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 16 May 2026 14:23:51 -0400
Subject: [PATCH 018/107] Fix live tape scroll hold and lazy history
---
.beads/issues.jsonl | 2 +-
apps/web/app/terminal.test.ts | 27 +--
apps/web/app/terminal.tsx | 87 ++++------
...6-05-16-live-tape-scroll-hold-history.html | 158 ++++++++++++++++++
services/api/src/live.ts | 82 +++++++--
services/api/tests/live.test.ts | 51 ++++++
6 files changed, 332 insertions(+), 75 deletions(-)
create mode 100644 docs/turns/2026-05-16-live-tape-scroll-hold-history.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index d2acc2b..065a612 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -10,7 +10,7 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","status":"in_progress","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:12:54Z","started_at":"2026-05-16T18:12:54Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","notes":"Implemented live scroll-hold with no live pause button, demand-loaded ClickHouse history, a 100-row options hot head, and cache-first scoped snapshots. Validated with bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts and bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:23:43Z","started_at":"2026-05-16T18:12:54Z","closed_at":"2026-05-16T18:23:43Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-c87","title":"Clean up duplicate Islandflow Docker infra on VPS","description":"The live VPS is currently running both the production-style islandflow-vps Docker stack and an older root-level islandflow infra stack that publishes NATS, ClickHouse, and Redis on host ports. Investigate whether the older stack is unused, remove it safely if so, and update docs/deploy guidance so the server topology is clearer.","notes":"Inspected the live VPS and confirmed the duplicate compose project: islandflow-vps is the supported deployment stack, while a separate islandflow project from the repo-root docker-compose.yml still runs exposed NATS/ClickHouse/Redis containers. Verified Nginx Proxy Manager routes only to islandflow-vps web/api by Docker name. Attempted cleanup via docker compose down and docker rm -f on the stale islandflow containers, but those commands hung for the delta user and timed out. Added repo guardrails and docs so deploy warns when the duplicate project exists, and opened islandflow-2db for manual host-level cleanup during a maintenance window.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:16:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:07Z","started_at":"2026-05-16T01:16:09Z","closed_at":"2026-05-16T01:28:07Z","close_reason":"Completed the repo-side investigation and guardrails. Actual server-side container removal is blocked by hanging Docker operations and is tracked separately in islandflow-2db for a maintenance window.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-4gj","title":"Clarify Docker-first deploy workflow and mark native runtime experimental","description":"After inspecting the live VPS, native deployment is not ready for routine use: Nginx Proxy Manager routes to Docker container names, Bun is not installed on the host, sudo systemctl is not passwordless, and no Islandflow units exist. Update deploy messaging and docs so Docker remains the clearly recommended deployment path and native runtime is labeled experimental/future-facing with server prerequisites called out.","notes":"Updated deploy messaging and docs after live VPS inspection. scripts/deploy.ts now marks Docker as the default and recommended runtime, labels native as experimental, switches native systemctl default to sudo -n systemctl, and prints explicit native precheck failures for missing Bun/systemctl access/units. Updated README.md, deployment/docker/README.md, and deployment/native/README.md to reflect the current Docker + Nginx Proxy Manager topology. Validation: ./deploy --help, ./deploy main --runtime native --no-build (fails fast with Bun-missing message), bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:10:11Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:12:39Z","started_at":"2026-05-16T01:10:14Z","closed_at":"2026-05-16T01:12:39Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/apps/web/app/terminal.test.ts b/apps/web/app/terminal.test.ts
index 8878fd9..0362723 100644
--- a/apps/web/app/terminal.test.ts
+++ b/apps/web/app/terminal.test.ts
@@ -164,6 +164,7 @@ describe("live manifest", () => {
expect(optionsSubscription?.underlying_ids).toEqual(["AAPL"]);
expect(optionsSubscription?.option_contract_id).toBe("AAPL-2025-01-17-200-C");
+ expect(optionsSubscription?.snapshot_limit).toBe(100);
expect(equitiesSubscription?.underlying_ids).toEqual(["AAPL"]);
});
@@ -635,23 +636,23 @@ describe("live tape history helpers", () => {
expect(next.map((item) => item.trace_id)).toEqual(["existing", "older-1"]);
});
- it("keeps scoped option and equity history on the normal retention cap", () => {
+ it("keeps option and equity history effectively unbounded while scrolling", () => {
expect(
getLiveHistoryRetentionCap({
channel: "options",
underlying_ids: ["AAPL"],
option_contract_id: "AAPL-2025-01-17-200-C"
} as any)
- ).toBeGreaterThan(0);
+ ).toBe(0);
expect(
getLiveHistoryRetentionCap({
channel: "equities",
underlying_ids: ["AAPL"]
} as any)
- ).toBeGreaterThan(0);
+ ).toBe(0);
});
- it("keeps auto-hydrating scoped live history while next_before exists", () => {
+ it("does not auto-hydrate scoped live history before the scroll gate is reached", () => {
const manifest = getLiveManifest(
"/tape",
"AAPL",
@@ -669,18 +670,12 @@ describe("live tape history helpers", () => {
expect(
getScopedLiveAutoHydrationChannels(true, "/tape", manifest, historyCursors, {})
- ).toEqual(["options", "equities"]);
+ ).toEqual([]);
expect(
getScopedLiveAutoHydrationChannels(true, "/tape", manifest, historyCursors, {
[getLiveSubscriptionKey(manifest.find((subscription) => subscription.channel === "options")!)]: true
})
- ).toEqual(["equities"]);
- expect(
- getScopedLiveAutoHydrationChannels(true, "/tape", manifest, {
- ...historyCursors,
- [getLiveSubscriptionKey(manifest.find((subscription) => subscription.channel === "equities")!)]: null
- }, {})
- ).toEqual(["options"]);
+ ).toEqual([]);
});
it("restores the same anchor key after live insertions at the top", () => {
@@ -864,9 +859,15 @@ describe("signals helpers", () => {
expect(getAlertWindowAnchorTs([], 42)).toBe(42);
});
- it("returns connected/stale live status labels without live wording", () => {
+ it("returns connected/held/stale live status labels without live wording", () => {
expect(statusLabel("connected", false, "live")).toBe("Connected");
+ expect(statusLabel("connected", true, "live")).toBe("Held");
expect(statusLabel("stale", false, "live")).toBe("Feed behind");
+ expect(statusLabel("stale", true, "live")).toBe("Feed behind");
+ });
+
+ it("keeps replay pause wording on replay tapes", () => {
+ expect(statusLabel("connected", true, "replay")).toBe("Paused");
});
it("treats healthy scoped channels as connected even when no matching rows are visible", () => {
diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx
index 20070fe..33eec33 100644
--- a/apps/web/app/terminal.tsx
+++ b/apps/web/app/terminal.tsx
@@ -77,6 +77,7 @@ const LIVE_HOT_WINDOW_OPTIONS = parseBoundedInt(
1,
100000
);
+const LIVE_OPTIONS_HEAD_LIMIT = 100;
const LIVE_HISTORY_SOFT_CAP = parseBoundedInt(
process.env.NEXT_PUBLIC_LIVE_HISTORY_SOFT_CAP,
5000,
@@ -846,7 +847,7 @@ export const getLiveHistoryRetentionCap = (subscription: LiveSubscription): numb
switch (subscription.channel) {
case "options":
case "equities":
- return LIVE_HISTORY_SOFT_CAP;
+ return 0;
default:
return LIVE_HISTORY_SOFT_CAP;
}
@@ -859,27 +860,12 @@ export const getScopedLiveAutoHydrationChannels = (
historyCursors: Partial>,
historyLoading: Partial>
): Array> => {
- if (!enabled || pathname !== "/tape") {
- return [];
- }
-
- const channels: Array> = [];
- for (const subscription of manifest) {
- const scoped =
- (subscription.channel === "options" &&
- (subscription.underlying_ids?.length || subscription.option_contract_id)) ||
- (subscription.channel === "equities" && subscription.underlying_ids?.length);
- if (!scoped) {
- continue;
- }
-
- const key = getLiveSubscriptionKey(subscription);
- if (historyCursors[key] && !historyLoading[key]) {
- channels.push(subscription.channel);
- }
- }
-
- return channels;
+ void enabled;
+ void pathname;
+ void manifest;
+ void historyCursors;
+ void historyLoading;
+ return [];
};
export const getLiveFeedStatus = (
@@ -2027,7 +2013,10 @@ export const prunePinnedEntries = (
export const statusLabel = (status: WsStatus, paused: boolean, mode: TapeMode): string => {
if (paused) {
- return "Paused";
+ if (mode === "replay") {
+ return "Paused";
+ }
+ return status === "connected" ? "Held" : statusLabel(status, false, mode);
}
if (mode === "replay") {
@@ -2512,22 +2501,20 @@ type PausableTapeViewConfig = {
const usePausableTapeView = (
config: PausableTapeViewConfig
): TapeState => {
- const [paused, setPaused] = useState(false);
const [data, setData] = useState>(EMPTY_PAUSABLE_TAPE);
+ const holdForScroll = config.enabled ? (config.shouldHold ? config.shouldHold() : false) : false;
useEffect(() => {
if (!config.enabled) {
- setPaused(false);
setData(EMPTY_PAUSABLE_TAPE);
return;
}
- const holdForScroll = config.shouldHold ? config.shouldHold() : false;
setData((current) => {
const next = reducePausableTapeData(
current,
config.sourceItems,
- paused || holdForScroll,
+ holdForScroll,
config.retentionLimit ?? LIVE_HOT_WINDOW
);
if (next === current) {
@@ -2535,7 +2522,7 @@ const usePausableTapeView = (
}
const unseenCount = next.seenKeys.size - current.seenKeys.size;
- if (!paused && unseenCount > 0) {
+ if (unseenCount > 0) {
config.onNewItems?.(unseenCount);
config.captureScroll?.();
}
@@ -2548,17 +2535,11 @@ const usePausableTapeView = (
config.onNewItems,
config.captureScroll,
config.retentionLimit,
- config.shouldHold,
- paused
+ holdForScroll
]);
useEffect(() => {
- if (!config.enabled || paused) {
- return;
- }
-
- const holdForScroll = config.shouldHold ? config.shouldHold() : false;
- if (holdForScroll) {
+ if (!config.enabled || holdForScroll) {
return;
}
@@ -2581,14 +2562,9 @@ const usePausableTapeView = (
config.onNewItems,
config.retentionLimit,
config.resumeSignal,
- config.shouldHold,
- paused
+ holdForScroll
]);
- const togglePause = useCallback(() => {
- setPaused((current) => !current);
- }, []);
-
const status = config.enabled ? config.sourceStatus : "disconnected";
const projected = projectPausableTapeState(data.visible, status, config.lastUpdate);
const historyItems = config.historyTail ?? [];
@@ -2602,9 +2578,9 @@ const usePausableTapeView = (
lastUpdate: projected.lastUpdate,
replayTime: null,
replayComplete: false,
- paused,
+ paused: holdForScroll,
dropped: data.dropped,
- togglePause
+ togglePause: () => {}
};
};
@@ -3052,7 +3028,7 @@ export const getLiveManifest = (
? undefined
: optionPrintFilters ?? flowFilters,
...optionScope,
- snapshot_limit: LIVE_HOT_WINDOW_OPTIONS
+ snapshot_limit: LIVE_OPTIONS_HEAD_LIMIT
});
}
if (features.nbbo) {
@@ -3337,7 +3313,7 @@ const useLiveSession = (
switch (subscription.channel) {
case "options":
- mergeItems(setOptions, optionsRef, items as OptionPrint[], LIVE_HOT_WINDOW_OPTIONS, {
+ mergeItems(setOptions, optionsRef, items as OptionPrint[], LIVE_OPTIONS_HEAD_LIMIT, {
setter: setOptionsHistory,
ref: optionsHistoryRef,
cap: getLiveHistoryRetentionCap(subscription)
@@ -3794,6 +3770,7 @@ const TapeStatus = ({
};
type TapeControlsProps = {
+ mode: TapeMode;
paused: boolean;
onTogglePause: () => void;
isAtTop: boolean;
@@ -3801,13 +3778,15 @@ type TapeControlsProps = {
onJump: () => void;
};
-const TapeControls = ({ paused, onTogglePause, isAtTop, missed, onJump }: TapeControlsProps) => {
+const TapeControls = ({ mode, paused, onTogglePause, isAtTop, missed, onJump }: TapeControlsProps) => {
const active = !isAtTop && missed > 0;
return (
Live tape now holds on scroll, resumes at top, and lazy-loads deep history
+
+ The live tape no longer depends on a manual pause button in live mode. Scrolling away from the top now holds the
+ tape automatically, Jump to top resumes it, the options hot head is capped at 100 rows, and older
+ history is fetched from ClickHouse only when the scroll gate requests it.
+
+
+
+
Created 2026-05-16 during issue islandflow-0sa.
+
+
+
Summary
+
+
+ This change aligns the tape with the intended operator workflow: hold the live head while investigating older
+ rows, keep historical prints valid even when old, and avoid preloading a large ClickHouse backlog until the
+ user actually scrolls into it.
+
+
+
+
+
+
Changes Made
+
+
+
Removed the live-mode Pause/Resume control from tape pane actions while keeping replay pause behavior intact.
+
Changed live tape status copy from manual Paused semantics to scroll-held Held.
+
Capped the live options head at 100 rows.
+
Stopped scoped live history from auto-hydrating in the background.
+
Made scoped options and equities snapshots prefer hot cached rows first, then backfill from ClickHouse when needed.
+
Made options and equities history retention effectively unbounded on the client so deep scrolling does not get trimmed away prematurely.
+
+
+
+
+
+
Context
+
+
+ The tape previously mixed several behaviors: a manual pause button, automatic scroll holding, scoped background
+ auto-hydration, and a much deeper options hot head. That created two user-visible problems: the live control model
+ felt redundant, and older prints could disappear or feel inconsistent when switching views or waiting for newer
+ rows to arrive.
+
+
+
+
+
+
Important Implementation Details
+
+
+
apps/web/app/terminal.tsx: live usePausableTapeView now treats scroll position as the hold source of truth.
+
apps/web/app/terminal.tsx: options live snapshot and retention now use a strict LIVE_OPTIONS_HEAD_LIMIT = 100.
+
apps/web/app/terminal.tsx: scoped history auto-hydration helper now returns no channels, so ClickHouse history stays lazy.
+
services/api/src/live.ts: scoped option/equity snapshots now filter the hot cache first, then merge ClickHouse backfill without seam duplicates.
Passed: bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts
+
Passed: bun --cwd=apps/web run build
+
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
+
Scoped snapshots can still backfill from ClickHouse when the hot cache does not have enough matching rows. This is intentional so focused views do not start empty.
+
Deep history is now lazy rather than eager, which reduces surprise and load, but the first deep-scroll request still depends on ClickHouse latency.
+
+
+
+
+
+
Follow-up Work
+
+
No additional follow-up issues were created in this turn.
+ {[
+ { label: "Signal", value: "signal" as const },
+ { label: "All prints", value: "raw" as const }
+ ].map((preset) => (
+
+ ))}
+
+
+ Signal keeps classifier-ready prints. All prints includes raw option tape rows.
+
+
+
{(["stock", "etf"] as OptionSecurityType[]).map((value) => (
diff --git a/docs/clickhouse-reset-runbook.md b/docs/clickhouse-reset-runbook.md
new file mode 100644
index 0000000..dac1775
--- /dev/null
+++ b/docs/clickhouse-reset-runbook.md
@@ -0,0 +1,57 @@
+# ClickHouse Reset Runbook
+
+This runbook is for deliberately wiping durable market-data history from ClickHouse in local development or on the VPS. It is destructive. Do not run these commands from application startup, deployment hooks, or unattended scripts.
+
+## When To Use
+
+Use this only when an operator has decided that existing option, equity, flow, and derived-event history should be discarded and rebuilt from fresh ingest.
+
+Before running a reset:
+
+- Confirm the target environment: local Docker or VPS Docker.
+- Confirm there is no active analysis depending on the existing history.
+- Take a backup if the data may be needed later.
+- Stop ingest and API services so new writes do not race the reset.
+
+## Local Docker Reset
+
+From the repository root:
+
+```bash
+bun run dev:infra
+docker compose exec clickhouse clickhouse-client --query "SHOW TABLES"
+docker compose exec clickhouse clickhouse-client --query "TRUNCATE TABLE IF EXISTS option_prints"
+docker compose exec clickhouse clickhouse-client --query "TRUNCATE TABLE IF EXISTS option_nbbo"
+docker compose exec clickhouse clickhouse-client --query "TRUNCATE TABLE IF EXISTS equity_prints"
+docker compose exec clickhouse clickhouse-client --query "TRUNCATE TABLE IF EXISTS equity_quotes"
+docker compose exec clickhouse clickhouse-client --query "TRUNCATE TABLE IF EXISTS equity_print_joins"
+docker compose exec clickhouse clickhouse-client --query "TRUNCATE TABLE IF EXISTS flow_packets"
+docker compose exec clickhouse clickhouse-client --query "TRUNCATE TABLE IF EXISTS smart_money_events"
+docker compose exec clickhouse clickhouse-client --query "TRUNCATE TABLE IF EXISTS classifier_hits"
+docker compose exec clickhouse clickhouse-client --query "TRUNCATE TABLE IF EXISTS alerts"
+docker compose exec clickhouse clickhouse-client --query "TRUNCATE TABLE IF EXISTS inferred_dark_events"
+```
+
+If the local compose project uses `deployment/docker/docker-compose.yml`, run the same commands with `docker compose -f deployment/docker/docker-compose.yml exec clickhouse ...`.
+
+## VPS Docker Reset
+
+On the VPS, first identify the active compose project and ClickHouse service:
+
+```bash
+docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
+docker compose -f deployment/docker/docker-compose.yml ps
+```
+
+Then stop writers and run the same `TRUNCATE TABLE IF EXISTS ...` commands against the active ClickHouse container. Prefer `docker compose exec clickhouse clickhouse-client --query ""` when the compose project is healthy; otherwise use `docker exec clickhouse-client --query ""`.
+
+## Verification
+
+After the reset:
+
+```bash
+docker compose exec clickhouse clickhouse-client --query "SELECT count() FROM option_prints"
+docker compose exec clickhouse clickhouse-client --query "SELECT count() FROM flow_packets"
+```
+
+Restart ingest/API services through the normal dev or deployment path. The options tape should repopulate its 100-row hot head from new signal prints, and older rows should appear only after the scroll gate asks `/history/options` for ClickHouse-backed history.
diff --git a/docs/turns/2026-05-16-1725-durable-options-tape-history.html b/docs/turns/2026-05-16-1725-durable-options-tape-history.html
new file mode 100644
index 0000000..a586496
--- /dev/null
+++ b/docs/turns/2026-05-16-1725-durable-options-tape-history.html
@@ -0,0 +1,245 @@
+
+
+
+
+
+ Durable Options Tape History
+
+
+
+
+
+
Turn Document
+
Durable Options Tape History
+
+ Implemented the durable options tape plan: the live hot head is capped at 100 rows, older rows are preserved behind
+ the scroll gate, ClickHouse history keeps execution context, and the Filter menu now exposes Signal versus All
+ prints semantics.
+
+ The options tape now behaves as a continuous instrument: the live cache stays lean, historical rows arrive only
+ when scrolling asks for them, and old valid rows are not treated as degraded just because they came from durable
+ history.
+
+
+
+
+
Changes Made
+
+
Changed the API default options live cache limit to 100.
+
Removed the unused scoped live auto-hydration path so history is loaded by the scroll gate.
+
Fixed unbounded options/equities history retention so a cap of 0 means keep the loaded tail.
+
Added a Filter menu Options View toggle for Signal and All prints.
+
Ensured All prints clears signal-only side/type/min-notional/security constraints.
+
Added a destructive ClickHouse reset runbook for local and VPS operators.
+
+
+
+
+
Context
+
+ The prior plan called out useful partial work already in the repo: ClickHouse history endpoints, execution-context
+ columns, scroll-hold behavior, and a shared row renderer. This implementation keeps those pieces and removes the
+ ambiguous history/autohydration behavior around them.
+
+
+
+
+
Important Implementation Details
+
+
/history/options still uses the selected option filters and scope, including raw contract drilldowns.
+
Storage tests now verify execution NBBO side, underlying spot, IV, and signal reasons survive normalization.
+
The options row path already preferred execution_nbbo_side, execution_underlying_spot, and execution_iv; tests cover that behavior.
+
The reset runbook is documented in docs/clickhouse-reset-runbook.md and is explicitly operator-confirmed.
+
+
+
+
+
Expected Impact for End-Users
+
+ Traders can stay on a signal-first tape by default, switch to raw prints when investigating, and scroll into older
+ ClickHouse-backed flow without seeing a separate stale-history treatment.
+
+
+
+
+
Validation
+
+
Passed:bun test packages/storage/tests/option-prints.test.ts services/api/tests/live.test.ts apps/web/app/terminal.test.ts
+
Passed:bun --cwd=apps/web run build
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
The ClickHouse reset remains destructive. Mitigation: documented as a manual runbook only, never automatic startup behavior.
+
No live browser smoke test was run in this turn. Mitigation: unit coverage and production build exercised the changed web paths.
+
+
+
+
+
Follow-up Work
+
No new follow-up issue was needed. The implementation task is tracked and completed in islandflow-200.
+ Make the options tape a signal-first live instrument with scroll-gated historical depth: keep the hot cache at
+ 100 option prints, load older rows from ClickHouse only at the scroll gate, preserve execution context, and
+ render ClickHouse-backed rows exactly like any other valid flow row.
+
+
+ Created 2026-05-16 17:11
+ Mode: Plan
+ Surface: Options Tape
+
+
+
+
+
Plan Summary
+
+ Treat stale strictly as feed health, not as historical-row quality. The user should be able to
+ analyze current live prints and earlier flow in one continuous tape, with no visual distinction between hot-cache
+ rows and ClickHouse-backed rows.
+
+
+
+
+
Goals
+
+
Keep the options tape scrolling infinitely from the user's perspective.
+
Hold only the 100 newest option prints in the hot live cache.
+
Use ClickHouse as the durable source for older rows once the scroll gate requests history.
+
Store all option-print data, including synthetic prints and execution context such as NBBO, spot, and IV.
+
Surface historical flow as real analyzable flow, not as stale, old, or degraded data.
+
Keep the default tape view signal-first while exposing all/raw prints from the existing Filter menu.
+ Stop tests and UI copy from asserting that valid rows older than 24 hours are stale when shown as
+ history.
+
+
+ Keep freshness gating only for live fanout/cache admission and channel health, not for historical validity.
+
+
+ Remove dead LiveHistoryBuffer and auto-hydration scaffolding if it remains unused after the flow is
+ explicit.
+
+
+ Keep the default options tape view as signal, and add a filter-menu view control with
+ Signal and All prints.
+
+
+ Ensure hot-cache rows and ClickHouse history rows use the same row component, same styling, same sorting, and
+ same interactions.
+
+
+ Keep cursor/key-based deduping so scroll-gated history does not duplicate the 100-row hot head.
+
+
+
+
+
+
Relevant Context
+
+
+ Prior work in islandflow-0sa already introduced scroll hold, a 100-row options head, lazy history,
+ and cache-first scoped snapshots.
+
+
+ The current storage/types path already includes execution context fields such as execution_nbbo_*,
+ execution_underlying_*, and execution_iv*.
+
+
+ Synthetic options prints already emit some execution context; the durable fix should verify this data survives
+ ClickHouse writes and reads.
+
+
+ The UI should prefer preserved execution context in row rendering before falling back to current NBBO lookup.
+
+
+ Beads has related work in islandflow-biq for raw live options delivery and filter/backpressure
+ observability.
+
+
+
+
+
+
Implementation Steps
+
+
+ Audit the existing options tape flow from ingest, ClickHouse write/read, live snapshot, history endpoint, and web
+ composition.
+
+
+ Adjust API/live semantics so valid ClickHouse history can be older than freshness thresholds without being treated
+ as degraded.
+
+
+ Add the Filter-menu view toggle for Signal and All prints, with short copy explaining
+ the difference.
+
+
+ Ensure buildOptionTapeQueryParams, live subscriptions, and /history/options all receive
+ the selected view consistently.
+
+
+ Confirm option row rendering uses preserved execution_nbbo_side, execution_underlying_spot,
+ and execution_iv when present.
+
+
+ Remove deprecated or unused history/autohydration code paths that no longer help the intended scroll-gated flow.
+
+
+ Add a deliberate reset path for local and VPS ClickHouse, documented as destructive and operator-confirmed.
+
+
+
+
+
+
Risks, Limitations, and Mitigations
+
+
+ Risk: Resetting VPS data is destructive. Mitigation: make it a runbook or explicit
+ command with backup/confirmation, never automatic app startup behavior.
+
+
+ Risk: The signal/raw toggle could affect both options and flow filters unexpectedly.
+ Mitigation: test option subscriptions, history query params, and flow packet filtering separately.
+
+
+ Risk: Older history fetch latency could be visible at the scroll gate. Mitigation:
+ keep lazy loading, expose loading/error state if needed, and avoid background auto-hydration.
+
+
+ Risk: Prior fixes may have left overlapping history logic. Mitigation: remove unused
+ scaffolding only after tests cover the intended hot-cache plus ClickHouse path.
+
+
+
+
+
+
Validation
+
+
+ Storage tests: fetchRecentOptionPrints and fetchOptionPrintsBefore return execution NBBO,
+ spot, IV, signal metadata, and raw/signal filtering correctly.
+
+
+ API/live tests: generic options snapshots cap at 100, scoped snapshots prefer hot cache, history preserves
+ next_before, and rows older than 24 hours return as valid history.
+
+
+ Web tests: Filter menu toggles Signal/All prints, scroll gate calls
+ loadOlder("options"), ClickHouse rows compose with no duplicate seam and no distinct styling, and
+ preserved execution context drives Spot, Side, Details, and IV display.
+
+
+ Validation commands: bun test packages/storage/tests/option-prints.test.ts services/api/tests/live.test.ts apps/web/app/terminal.test.ts
+ and bun --cwd=apps/web run build.
+
+
+
+
+
+
Open Questions
+
+
+ Exact VPS reset command sequence should be confirmed against the live deployment state before execution.
+
+
+ Decide during implementation whether to track the reset/runbook in a new Beads issue or fold it into
+ islandflow-biq.
+
+
+
+
+ Fixed assumptions: historical ClickHouse rows should be visually indistinguishable from hot-cache rows, and local
+ plus VPS wipe should be an operator-confirmed reset path rather than a background migration.
+
+
+
+
+
+
From 23ed3809cc111155e5b1dba4e387f3e8b62b630b Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 16 May 2026 17:54:00 -0400
Subject: [PATCH 022/107] speed up docker deploy builds
---
.beads/issues.jsonl | 2 +
deployment/docker/Dockerfile.ingest-options | 38 ++-
deployment/docker/Dockerfile.service | 26 ++-
deployment/docker/Dockerfile.web | 25 +-
deployment/docker/README.md | 35 ++-
...26-05-16-1752-speed-up-docker-deploys.html | 219 ++++++++++++++++++
scripts/deploy.ts | 26 ++-
7 files changed, 349 insertions(+), 22 deletions(-)
create mode 100644 docs/turns/2026-05-16-1752-speed-up-docker-deploys.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 605077e..1ac2304 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -11,6 +11,7 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-09a","title":"Speed up Docker deployment builds","description":"Implement the Docker deployment optimization plan from /Users/kell/Desktop/speed-up-docker.md: split dependency installation from source copy, add BuildKit caches, make scoped deploys build only their target services, update Docker deployment docs, validate, document the turn, commit, and push.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:50:24Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:53:48Z","started_at":"2026-05-16T21:50:37Z","closed_at":"2026-05-16T21:53:48Z","close_reason":"Implemented Docker dependency-layer caching, scoped deploy build/up flow, Docker docs updates, validation, and turn documentation. Follow-up islandflow-cnk tracks daemon-backed image build verification.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","notes":"Implemented live scroll-hold with no live pause button, demand-loaded ClickHouse history, a 100-row options hot head, and cache-first scoped snapshots. Validated with bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts and bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:23:43Z","started_at":"2026-05-16T18:12:54Z","closed_at":"2026-05-16T18:23:43Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-c87","title":"Clean up duplicate Islandflow Docker infra on VPS","description":"The live VPS is currently running both the production-style islandflow-vps Docker stack and an older root-level islandflow infra stack that publishes NATS, ClickHouse, and Redis on host ports. Investigate whether the older stack is unused, remove it safely if so, and update docs/deploy guidance so the server topology is clearer.","notes":"Inspected the live VPS and confirmed the duplicate compose project: islandflow-vps is the supported deployment stack, while a separate islandflow project from the repo-root docker-compose.yml still runs exposed NATS/ClickHouse/Redis containers. Verified Nginx Proxy Manager routes only to islandflow-vps web/api by Docker name. Attempted cleanup via docker compose down and docker rm -f on the stale islandflow containers, but those commands hung for the delta user and timed out. Added repo guardrails and docs so deploy warns when the duplicate project exists, and opened islandflow-2db for manual host-level cleanup during a maintenance window.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:16:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:07Z","started_at":"2026-05-16T01:16:09Z","closed_at":"2026-05-16T01:28:07Z","close_reason":"Completed the repo-side investigation and guardrails. Actual server-side container removal is blocked by hanging Docker operations and is tracked separately in islandflow-2db for a maintenance window.","dependency_count":0,"dependent_count":0,"comment_count":0}
@@ -38,5 +39,6 @@
{"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-cnk","title":"Run Docker image build verification with active Docker daemon","description":"Targeted image builds could not run in the implementation session because the local Docker daemon was unavailable at unix:///Users/kell/.orbstack/run/docker.sock. When Docker or OrbStack is running, validate the refactored deployment Dockerfiles with: docker compose -f deployment/docker/docker-compose.yml build api; docker compose -f deployment/docker/docker-compose.yml build web; docker compose -f deployment/docker/docker-compose.yml build ingest-options.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:53:41Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:53:41Z","dependencies":[{"issue_id":"islandflow-cnk","depends_on_id":"islandflow-09a","type":"discovered-from","created_at":"2026-05-16T17:53:40Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-38p","title":"Add native deployment unit templates and rollback helpers","description":"The deploy helper now supports --runtime native, but the repo still relies on operator-managed systemd units and manual rollback. Add checked-in native deployment templates or provisioning guidance for the expected units, and consider lightweight rollback/smoke-test helpers once the host-native path is exercised on the real VPS.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:46:42Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-575","title":"Document smart-money event calendar env","description":"Document smart-money event-calendar environment configuration in env examples and README.\n","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T06:57:14Z","created_by":"dirtydishes","updated_at":"2026-05-05T06:57:57Z","started_at":"2026-05-05T06:57:17Z","closed_at":"2026-05-05T06:57:57Z","close_reason":"Documented event-calendar env variables","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/deployment/docker/Dockerfile.ingest-options b/deployment/docker/Dockerfile.ingest-options
index 156dc1d..52cba59 100644
--- a/deployment/docker/Dockerfile.ingest-options
+++ b/deployment/docker/Dockerfile.ingest-options
@@ -1,3 +1,5 @@
+# syntax=docker/dockerfile:1.7
+
FROM oven/bun:1.3.11
WORKDIR /app
@@ -9,15 +11,39 @@ ENV PATH="${VIRTUAL_ENV}/bin:${PATH}"
COPY --from=workspace package.json ./package.json
COPY --from=workspace bun.lock ./bun.lock
COPY --from=workspace tsconfig.base.json ./tsconfig.base.json
-COPY --from=services . ./services
-COPY --from=packages . ./packages
-COPY --from=apps . ./apps
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 python3-pip python3-venv \
&& rm -rf /var/lib/apt/lists/* \
- && python3 -m venv "${VIRTUAL_ENV}" \
- && "${VIRTUAL_ENV}/bin/pip" install --no-cache-dir -r services/ingest-options/py/requirements.txt \
- && bun install --frozen-lockfile
+ && python3 -m venv "${VIRTUAL_ENV}"
+
+COPY --from=apps desktop/package.json ./apps/desktop/package.json
+COPY --from=apps web/package.json ./apps/web/package.json
+
+COPY --from=packages bus/package.json ./packages/bus/package.json
+COPY --from=packages config/package.json ./packages/config/package.json
+COPY --from=packages observability/package.json ./packages/observability/package.json
+COPY --from=packages storage/package.json ./packages/storage/package.json
+COPY --from=packages types/package.json ./packages/types/package.json
+
+COPY --from=services api/package.json ./services/api/package.json
+COPY --from=services candles/package.json ./services/candles/package.json
+COPY --from=services compute/package.json ./services/compute/package.json
+COPY --from=services eod-enricher/package.json ./services/eod-enricher/package.json
+COPY --from=services ingest-equities/package.json ./services/ingest-equities/package.json
+COPY --from=services ingest-options/package.json ./services/ingest-options/package.json
+COPY --from=services ingest-options/py/requirements.txt ./services/ingest-options/py/requirements.txt
+COPY --from=services refdata/package.json ./services/refdata/package.json
+COPY --from=services replay/package.json ./services/replay/package.json
+
+RUN --mount=type=cache,target=/root/.cache/pip \
+ "${VIRTUAL_ENV}/bin/pip" install -r services/ingest-options/py/requirements.txt
+
+RUN --mount=type=cache,target=/root/.bun/install/cache \
+ bun install --frozen-lockfile
+
+COPY --from=services . ./services
+COPY --from=packages . ./packages
+COPY --from=apps . ./apps
ENTRYPOINT ["bun"]
diff --git a/deployment/docker/Dockerfile.service b/deployment/docker/Dockerfile.service
index bc48d2d..e0fcf72 100644
--- a/deployment/docker/Dockerfile.service
+++ b/deployment/docker/Dockerfile.service
@@ -1,3 +1,5 @@
+# syntax=docker/dockerfile:1.7
+
FROM oven/bun:1.3.11
WORKDIR /app
@@ -7,10 +9,30 @@ ENV NODE_ENV=production
COPY --from=workspace package.json ./package.json
COPY --from=workspace bun.lock ./bun.lock
COPY --from=workspace tsconfig.base.json ./tsconfig.base.json
+
+COPY --from=apps desktop/package.json ./apps/desktop/package.json
+COPY --from=apps web/package.json ./apps/web/package.json
+
+COPY --from=packages bus/package.json ./packages/bus/package.json
+COPY --from=packages config/package.json ./packages/config/package.json
+COPY --from=packages observability/package.json ./packages/observability/package.json
+COPY --from=packages storage/package.json ./packages/storage/package.json
+COPY --from=packages types/package.json ./packages/types/package.json
+
+COPY --from=services api/package.json ./services/api/package.json
+COPY --from=services candles/package.json ./services/candles/package.json
+COPY --from=services compute/package.json ./services/compute/package.json
+COPY --from=services eod-enricher/package.json ./services/eod-enricher/package.json
+COPY --from=services ingest-equities/package.json ./services/ingest-equities/package.json
+COPY --from=services ingest-options/package.json ./services/ingest-options/package.json
+COPY --from=services refdata/package.json ./services/refdata/package.json
+COPY --from=services replay/package.json ./services/replay/package.json
+
+RUN --mount=type=cache,target=/root/.bun/install/cache \
+ bun install --frozen-lockfile
+
COPY --from=services . ./services
COPY --from=packages . ./packages
COPY --from=apps . ./apps
-RUN bun install --frozen-lockfile
-
ENTRYPOINT ["bun"]
diff --git a/deployment/docker/Dockerfile.web b/deployment/docker/Dockerfile.web
index 6956335..33723ae 100644
--- a/deployment/docker/Dockerfile.web
+++ b/deployment/docker/Dockerfile.web
@@ -1,3 +1,5 @@
+# syntax=docker/dockerfile:1.7
+
FROM oven/bun:1.3.11 AS build
WORKDIR /app
@@ -13,11 +15,32 @@ ENV NEXT_PUBLIC_NBBO_MAX_AGE_MS=${NEXT_PUBLIC_NBBO_MAX_AGE_MS}
COPY --from=workspace package.json ./package.json
COPY --from=workspace bun.lock ./bun.lock
COPY --from=workspace tsconfig.base.json ./tsconfig.base.json
+
+COPY --from=apps desktop/package.json ./apps/desktop/package.json
+COPY --from=apps web/package.json ./apps/web/package.json
+
+COPY --from=packages bus/package.json ./packages/bus/package.json
+COPY --from=packages config/package.json ./packages/config/package.json
+COPY --from=packages observability/package.json ./packages/observability/package.json
+COPY --from=packages storage/package.json ./packages/storage/package.json
+COPY --from=packages types/package.json ./packages/types/package.json
+
+COPY --from=services api/package.json ./services/api/package.json
+COPY --from=services candles/package.json ./services/candles/package.json
+COPY --from=services compute/package.json ./services/compute/package.json
+COPY --from=services eod-enricher/package.json ./services/eod-enricher/package.json
+COPY --from=services ingest-equities/package.json ./services/ingest-equities/package.json
+COPY --from=services ingest-options/package.json ./services/ingest-options/package.json
+COPY --from=services refdata/package.json ./services/refdata/package.json
+COPY --from=services replay/package.json ./services/replay/package.json
+
+RUN --mount=type=cache,target=/root/.bun/install/cache \
+ bun install --frozen-lockfile
+
COPY --from=services . ./services
COPY --from=packages . ./packages
COPY --from=apps . ./apps
-RUN bun install --frozen-lockfile
RUN bun run --cwd apps/web build
FROM oven/bun:1.3.11 AS runtime
diff --git a/deployment/docker/README.md b/deployment/docker/README.md
index 7c4f03b..4a5019f 100644
--- a/deployment/docker/README.md
+++ b/deployment/docker/README.md
@@ -65,14 +65,16 @@ Important defaults:
3. Build and start the stack:
```bash
-docker compose up -d --build
+docker compose build api web compute candles ingest-options ingest-equities
+docker compose up -d
```
If you are updating an existing deployment that already has failing `api` restart loops, do a full recreate so the ClickHouse config mount and dependency changes are applied cleanly:
```bash
docker compose down
-docker compose up -d --build --force-recreate
+docker compose build api web compute candles ingest-options ingest-equities
+docker compose up -d --force-recreate
```
4. Confirm the containers are healthy:
@@ -190,6 +192,19 @@ cd deployment/docker
docker compose build web
```
+### Faster Docker builds
+
+The app images are structured so dependency installation is isolated from source code changes:
+
+- Docker first copies `package.json`, `bun.lock`, `tsconfig.base.json`, and workspace `package.json` files.
+- `bun install --frozen-lockfile` runs in a cacheable layer with a BuildKit Bun cache mount.
+- Source from `apps`, `services`, and `packages` is copied only after dependencies are installed.
+- `ingest-options` also installs its Python sidecar dependencies from `services/ingest-options/py/requirements.txt` before source copy, using a BuildKit pip cache mount.
+
+That means normal TypeScript edits should reuse dependency layers. The first build after a fresh server checkout, Docker cache cleanup, dependency change, or Python requirement change can still be slow; later deploys should spend their time on changed source and the specific service images being rolled out.
+
+BuildKit cache mounts require a modern Docker Engine with Dockerfile frontend support. Docker Compose v2 on the VPS path enables this by default.
+
## Safe rollouts on `152.53.80.229`
The current live VPS uses Nginx Proxy Manager on the shared Docker network and routes public traffic to the Docker `web` and `api` containers by container name. Because of that, this Docker path remains the operationally correct default for the live server today.
@@ -218,7 +233,7 @@ This flow:
- checks the server checkout before switching anything
- stops if the server has tracked local modifications
- allows the known untracked tarball at `deployment/docker/signal-cli-0.14.3-Linux-native.tar.gz`
-- runs `git switch main`, `git pull --ff-only origin main`, and `docker compose up -d --build`
+- runs `git switch main`, `git pull --ff-only origin main`, `docker compose build api web compute candles ingest-options ingest-equities`, and `docker compose up -d`
- verifies the stack with `docker compose ps`, recent service logs, container-local health checks, and public HTTPS checks
### Deploy the current local branch
@@ -253,6 +268,14 @@ Examples:
./deploy main --runtime docker --web-only --no-build
```
+Scoped Docker deploys now build only the selected image set and then restart only those services:
+
+- `--web-only`: `docker compose build web`, then `docker compose up -d web`
+- `--api-only`: `docker compose build api`, then `docker compose up -d api`
+- `--services-only`: builds and restarts `api`, `compute`, `candles`, `ingest-options`, and `ingest-equities`
+
+Use `--no-build` only when the image is already correct and you need Compose to recreate or restart containers, such as after changing server-side environment values that do not affect a Next.js build-time variable. Do not use `--no-build` for dependency changes, application source changes, or `NEXT_PUBLIC_*` changes.
+
### Escalation path
Use force recreate only when a normal refresh does not update the services cleanly:
@@ -299,7 +322,8 @@ git switch main
git pull --ff-only origin main
cd /home/delta/islandflow/deployment/docker
-docker compose up -d --build
+docker compose build api web compute candles ingest-options ingest-equities
+docker compose up -d
```
Deploy the current branch manually:
@@ -314,7 +338,8 @@ git switch || git switch -c --track origin/
cd /home/delta/islandflow/deployment/docker
-docker compose up -d --build
+docker compose build api web compute candles ingest-options ingest-equities
+docker compose up -d
```
If you changed only env values for the Bun services on the server:
diff --git a/docs/turns/2026-05-16-1752-speed-up-docker-deploys.html b/docs/turns/2026-05-16-1752-speed-up-docker-deploys.html
new file mode 100644
index 0000000..df16d62
--- /dev/null
+++ b/docs/turns/2026-05-16-1752-speed-up-docker-deploys.html
@@ -0,0 +1,219 @@
+
+
+
+
+
+ Speed Up Docker Deploys
+
+
+
+
+
+
2026-05-16 17:52 America/New_York
+
Speed Up Docker Deploys
+
+ Summary
+ Docker app images now cache dependency installation separately from source changes, and Docker rollouts now build only the images required by the selected deploy scope before restarting containers.
+
+
+
+
+
Summary
+
+ Implemented the Docker deployment speed-up plan from /Users/kell/Desktop/speed-up-docker.md. The first build after this change may still be slow, but source-only changes should no longer invalidate the expensive Bun and Python dependency layers.
+
+
+
+
+
Changes Made
+
+
Refactored deployment/docker/Dockerfile.service to copy workspace manifests, run cached bun install --frozen-lockfile, then copy source.
+
Applied the same dependency-first build model to deployment/docker/Dockerfile.web, keeping the Next.js build after source copy.
+
Updated deployment/docker/Dockerfile.ingest-options with separate cached pip and Bun install layers before copying source.
+
Changed scripts/deploy.ts so Docker rollouts run explicit docker compose build <services> followed by docker compose up -d <services>.
+
Documented the faster-build model, scoped rollouts, and appropriate --no-build usage in deployment/docker/README.md.
+
+
+
+
+
Context
+
+ The previous Dockerfiles copied all app, service, and package source before dependency installation. That made nearly every code change invalidate bun install, increasing VPS deploy time. The deployment helper also used broad up -d --build behavior rather than a clean build phase scoped to the selected service set.
+
+
+
+
+
Important Implementation Details
+
+ Each app image now copies root deployment manifests plus every workspace package.json before installing dependencies. The source tree is copied only after the install layer is complete.
+
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
+ bun install --frozen-lockfile
+
+ The ingest-options image also copies services/ingest-options/py/requirements.txt before source and uses a pip cache mount:
+
+
RUN --mount=type=cache,target=/root/.cache/pip \
+ "${VIRTUAL_ENV}/bin/pip" install -r services/ingest-options/py/requirements.txt
+
+ For full Docker deploys, the helper builds the six core app services explicitly. For scoped deploys, it builds and restarts only the requested services.
+
+
+
+
+
Expected Impact for End-Users
+
+ Users should see faster deployment turnaround after ordinary source edits because dependency installation is reused when manifests and locks have not changed. Scoped deploys should also disturb fewer containers, reducing restart surface for web-only, API-only, and backend-only updates.
+
+
+
+
+
Validation
+
+
Passed:bun run check:docker-workspace
+
Passed:./deploy --help
+
Passed:docker compose -f deployment/docker/docker-compose.yml config --quiet with a temporary copy of .env.example
+
Passed:bun --cwd=apps/web run build
+
Passed:bun test with 222 passing tests
+
Not run: targeted Docker image builds because this session could not connect to the Docker daemon at unix:///Users/kell/.orbstack/run/docker.sock.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+ Docker daemon access was unavailable locally, so image builds still need to be exercised on a machine with a running Docker daemon or during the next VPS rollout. Static Compose validation and repo test coverage passed, and the Dockerfiles use standard BuildKit cache mounts supported by modern Docker Compose v2.
+
+
+
+
+
Follow-up Work
+
+ No separate follow-up issue was created. The remaining verification is operational: run the targeted image builds once Docker or OrbStack is available.
+
+
+
+
+
diff --git a/scripts/deploy.ts b/scripts/deploy.ts
index 1ec3e6c..d6adcb1 100644
--- a/scripts/deploy.ts
+++ b/scripts/deploy.ts
@@ -324,6 +324,15 @@ function dockerServicesForScope(scope: DeployScope): string[] {
}
}
+function dockerBuildServicesForScope(scope: DeployScope): string[] {
+ switch (scope) {
+ case "full":
+ return [...DOCKER_CORE_SERVICES];
+ default:
+ return dockerServicesForScope(scope);
+ }
+}
+
function dockerLogServicesForScope(scope: DeployScope): string[] {
switch (scope) {
case "web":
@@ -565,15 +574,16 @@ function remoteDockerRollout(
forceRecreate: boolean,
noBuild: boolean
): void {
- const services = dockerServicesForScope(scope);
- const args = ["up", "-d"];
- if (!noBuild) {
- args.push("--build");
- }
+ const rolloutServices = dockerServicesForScope(scope);
+ const upArgs = ["up", "-d"];
if (forceRecreate) {
- args.push("--force-recreate");
+ upArgs.push("--force-recreate");
}
- const command = `docker compose ${[...args, ...services].join(" ")}`;
+ const buildServices = dockerBuildServicesForScope(scope);
+ const buildCommand = noBuild
+ ? null
+ : `docker compose build ${buildServices.join(" ")}`;
+ const upCommand = `docker compose ${[...upArgs, ...rolloutServices].join(" ")}`;
runRemoteScript(
"Remote Rollout",
@@ -583,7 +593,7 @@ set -euo pipefail
${remoteGitUpdateScript(mode, branch)}
cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)}
-${command}
+${buildCommand ? `${buildCommand}\n` : ""}${upCommand}
`
);
}
From 1424a2716fc7d53863bf3df36428464301406bac Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 16 May 2026 22:00:21 -0400
Subject: [PATCH 023/107] fix durable options history routing
---
.beads/issues.jsonl | 2 +
apps/web/app/globals.css | 11 +
apps/web/app/terminal.tsx | 12 ++
deployment/docker/README.md | 9 +-
...9-fix-durable-options-history-routing.html | 195 ++++++++++++++++++
package.json | 1 +
scripts/check-public-api-routes.ts | 41 ++++
scripts/deploy.ts | 4 +-
8 files changed, 271 insertions(+), 4 deletions(-)
create mode 100644 docs/turns/2026-05-16-2159-fix-durable-options-history-routing.html
create mode 100644 scripts/check-public-api-routes.ts
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 1ac2304..2bf9d72 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,3 +1,4 @@
+{"_type":"issue","id":"islandflow-qso","title":"Fix durable options tape history routing","description":"Implement the fix-tape plan: make same-origin history routing durable, add deployment/public smoke checks for required API routes, expose tape history loading failures in the UI, document the work, and track api.flow.deltaisland.io migration separately.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T01:53:22Z","created_by":"dirtydishes","updated_at":"2026-05-17T02:00:04Z","started_at":"2026-05-17T01:53:25Z","closed_at":"2026-05-17T02:00:04Z","close_reason":"Implemented durable same-origin history routing, public route smoke checks, tape history diagnostics, docs, validation, and follow-up tracking for api.flow.deltaisland.io.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-200","title":"Implement durable options tape history","description":"Implement the plan from docs/plans/2026-05-16-1711-durable-options-tape-history.html: durable ClickHouse-backed options history, signal/all prints view selection, preserved execution context, stale semantics limited to live health, reset runbook, tests, and turn documentation.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:21:30Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:26:51Z","started_at":"2026-05-16T21:21:33Z","closed_at":"2026-05-16T21:26:51Z","close_reason":"Implemented durable options tape history, signal/raw view selection, reset runbook, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-k4f","title":"Gate deploy script on docker workspace snapshot sync","description":"Prevent frozen-lockfile build failures during deploy by adding a local preflight in scripts/deploy.ts that runs bun run check:docker-workspace and aborts with a clear sync+commit remediation message when stale.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:01:44Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:04:11Z","started_at":"2026-05-15T23:01:48Z","closed_at":"2026-05-15T23:04:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-xll","title":"Fix bun.lock drift causing frozen-lockfile Docker build failures","description":"Docker image builds fail in multiple targets (candles, web, ingest services) because bun install --frozen-lockfile detects lockfile changes. Update workspace lockfile to match manifests and verify frozen install succeeds.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T22:52:38Z","created_by":"dirtydishes","updated_at":"2026-05-15T22:55:23Z","started_at":"2026-05-15T22:52:40Z","closed_at":"2026-05-15T22:55:23Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
@@ -11,6 +12,7 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-qd7","title":"Migrate production web to api.flow.deltaisland.io","description":"Follow-up from the durable options tape history fix. Plan and migrate production from same-origin API path proxying on flow.deltaisland.io to a dedicated api.flow.deltaisland.io origin, including DNS, proxy config, CORS/websocket behavior, deployment docs, and public smoke checks.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-17T01:55:02Z","created_by":"dirtydishes","updated_at":"2026-05-17T01:55:02Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-09a","title":"Speed up Docker deployment builds","description":"Implement the Docker deployment optimization plan from /Users/kell/Desktop/speed-up-docker.md: split dependency installation from source copy, add BuildKit caches, make scoped deploys build only their target services, update Docker deployment docs, validate, document the turn, commit, and push.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:50:24Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:53:48Z","started_at":"2026-05-16T21:50:37Z","closed_at":"2026-05-16T21:53:48Z","close_reason":"Implemented Docker dependency-layer caching, scoped deploy build/up flow, Docker docs updates, validation, and turn documentation. Follow-up islandflow-cnk tracks daemon-backed image build verification.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","notes":"Implemented live scroll-hold with no live pause button, demand-loaded ClickHouse history, a 100-row options hot head, and cache-first scoped snapshots. Validated with bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts and bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:23:43Z","started_at":"2026-05-16T18:12:54Z","closed_at":"2026-05-16T18:23:43Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css
index 1b2205c..a0e1822 100644
--- a/apps/web/app/globals.css
+++ b/apps/web/app/globals.css
@@ -1003,6 +1003,17 @@ h3 {
overflow: hidden;
}
+.history-load-warning {
+ flex: 0 0 auto;
+ padding: 8px 12px;
+ border-top: 1px solid oklch(0.72 0.13 58 / 0.45);
+ border-bottom: 1px solid oklch(0.72 0.13 58 / 0.45);
+ background: oklch(0.24 0.05 58 / 0.72);
+ color: oklch(0.91 0.08 72);
+ font-size: 0.78rem;
+ line-height: 1.35;
+}
+
.data-table-wrap {
display: flex;
flex: 1 1 auto;
diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx
index 2135a75..1cd6f42 100644
--- a/apps/web/app/terminal.tsx
+++ b/apps/web/app/terminal.tsx
@@ -7109,6 +7109,13 @@ type OptionsPaneProps = {
const OptionsPane = memo(({ state, limit }: OptionsPaneProps) => {
const items = limit ? state.filteredOptions.slice(0, limit) : state.filteredOptions;
const virtual = useTapeVirtualList(items, state.optionsScroll.listRef, getTapeVirtualConfig("options"));
+ const optionHistorySubscription = state.liveSession.manifest.find(
+ (subscription) => subscription.channel === "options"
+ );
+ const optionHistoryKey = optionHistorySubscription ? getLiveSubscriptionKey(optionHistorySubscription) : null;
+ const optionHistoryError = optionHistoryKey
+ ? state.liveSession.historyErrors[optionHistoryKey]
+ : null;
useVirtualHistoryGate(state.mode === "live" && !limit, items.length, virtual.virtualItems.at(-1)?.index ?? -1, () =>
void state.liveSession.loadOlder("options")
);
@@ -7139,6 +7146,11 @@ const OptionsPane = memo(({ state, limit }: OptionsPaneProps) => {
}
>
+ Older option history failed to load: {optionHistoryError}
+
+ ) : null}
{items.length === 0 ? (
{state.mode === "live"
diff --git a/deployment/docker/README.md b/deployment/docker/README.md
index 4a5019f..0f5c886 100644
--- a/deployment/docker/README.md
+++ b/deployment/docker/README.md
@@ -119,10 +119,16 @@ Supported routing modes:
- Build web with `NEXT_PUBLIC_API_URL=` (empty).
- Point `app.` at the web host port.
- Proxy these API routes from the app origin to the API host port:
- - `/ws/*`, `/replay/*`, `/prints/*`, `/joins/*`, `/nbbo/*`, `/dark/*`, `/flow/*`, `/candles/*`
+ - `/ws/*`, `/replay/*`, `/prints/*`, `/joins/*`, `/nbbo/*`, `/dark/*`, `/flow/*`, `/candles/*`, `/history/*`
Enable websocket support on whichever host serves `/ws/*`.
+For the current live Nginx Proxy Manager setup behind `flow.deltaisland.io`, keep the API location regex durable in the proxy host advanced config or API, not by hand-editing generated files under `/data/nginx/proxy_host/`. The route matcher should include history:
+
+```nginx
+^/(ws|replay|prints|joins|nbbo|dark|flow|candles|history)/
+```
+
## Replay service
Replay is disabled by default in this stack.
@@ -441,3 +447,4 @@ After the stack is up:
- `curl -I http://127.0.0.1:3000/` should return a successful HTTP status on the server.
- In two-origin mode, browser requests should target `https://api./...` and live feeds should use `wss://api./ws/...`.
- In same-origin mode, browser requests should target `https://app./...` for API paths and live feeds should use `wss://app./ws/...`.
+- In same-origin mode, `bun run check:public-api-routes` should pass for `/prints/options`, `/history/options`, `/replay/options`, `/nbbo/options`, and `/ws/live`.
diff --git a/docs/turns/2026-05-16-2159-fix-durable-options-history-routing.html b/docs/turns/2026-05-16-2159-fix-durable-options-history-routing.html
new file mode 100644
index 0000000..62be8b7
--- /dev/null
+++ b/docs/turns/2026-05-16-2159-fix-durable-options-history-routing.html
@@ -0,0 +1,195 @@
+
+
+
+
+
+ Fix Durable Options History Routing
+
+
+
+
+
+ Validated
+
Fix Durable Options History Routing
+
Turn completed on 2026-05-16 21:59 America/New_York.
+
+
+
+
Summary
+
+ Options tape history now has a durable public route through same-origin deployments. The live Nginx Proxy Manager route was updated to include /history/*, deployment checks now fail when required API paths reach the web app, and the tape UI surfaces older-history load failures instead of leaving the user to infer that only the hot window exists.
+
+
+
+
+
Changes Made
+
+
Added scripts/check-public-api-routes.ts and the check:public-api-routes package script.
+
Updated scripts/deploy.ts so same-origin API deploy verification probes required public routes.
+
Updated deployment/docker/README.md to include /history/* in same-origin proxy routing and document the Nginx Proxy Manager regex.
+
Added an options tape warning banner for live /history/options load errors.
+
Updated live Nginx Proxy Manager config for flow.deltaisland.io so the public route regex includes history.
+
Created follow-up Beads issue islandflow-qd7 for the later api.flow.deltaisland.io migration.
+
+
+
+
+
Context
+
+ The API and ClickHouse path already supported older options history, but the public same-origin route sent /history/options to the Next.js app. That made the live tape feel capped at the newest hot-window rows even though durable history existed behind the API.
+
+
+
+
+
Important Implementation Details
+
+ The deploy smoke check performs GET probes and verifies JSON responses for these same-origin routes:
+
+ Users on /tape can scroll beyond the initial options hot window and receive older ClickHouse-backed rows through the same cursor path for Signal and All prints. If public routing regresses, the tape now shows a visible history loading failure.
+
+
+
+
+
Validation
+
+
Passed: bun test apps/web/app/terminal.test.ts
+
Passed: bun test
+
Passed: bun --cwd=apps/web run build
+
Passed: bun run check:public-api-routes
+
Passed: remote Nginx syntax check after updating the route.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
The long-term API subdomain migration remains separate work. Mitigation: tracked as islandflow-qd7.
+
The Nginx Proxy Manager database and generated proxy host file were both updated because the existing live file had prior generated-file edits. Mitigation: deployment docs now call out the durable advanced-config/API path.
+
+
+
+
+
Follow-up Work
+
+ Complete islandflow-qd7 to move production API traffic to api.flow.deltaisland.io deliberately, including DNS, proxy behavior, CORS/websocket checks, docs, and deployment verification.
+
+ Completed on 2026-05-17 at 03:31 America/New_York for Beads issue
+ islandflow-9dg.
+
+
+
+
+
Summary
+
+ The live tape now keeps the visible scrolled segment stable while new prints arrive. When
+ the user is away from the top, the view freezes both the hot live head and the displayed
+ history segment, only allowing genuinely older history to append below the current tail.
+
+
+
+
+
Changes Made
+
+
Added mergeHeldTapeHistory to filter held history updates by the visible tail.
+
Updated usePausableTapeView to keep a displayed history ref while scroll-held.
+
Resynced displayed history automatically when the user jumps back to the top or otherwise resumes.
+
Increased tape virtualizer overscan for options, equities, flow, alerts, classifier, and dark panes.
+
Added a fixed row-lane table background so fast scrolling shows a stable substrate instead of blank holes.
+
+
+
+
+
Context
+
+ Live session history receives both ClickHouse history and hot-window overflow from new live
+ prints. Before this change, the pausable view froze live rows during scroll hold but still
+ composed against the mutating history array, so newer overflow rows could insert above the
+ user's current viewport.
+
+
+
+
+
Important Implementation Details
+
+ The stable merge compares incoming history with the current displayed history tail. Rows
+ newer than that tail are withheld during hold, duplicates from the frozen live head are
+ removed, and older lazy-loaded rows remain eligible to append.
+
+
const next = mergeHeldTapeHistory(displayedHistoryRef.current, historyItems, projected.items);
+
+ When hold ends, displayedHistoryRef is replaced with the latest live session
+ history, so buffered overflow catches up cleanly on jump-to-top.
+
+
+
+
+
Expected Impact for End-Users
+
+ Users can scroll into older options or equities prints without the rows shifting under them
+ as new live prints arrive. The +N new counter can continue accumulating until
+ they jump back to the top, where the tape catches up.
+
+
+
+
+
Validation
+
+
bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts: passed, 90 tests.
+
bun --cwd=apps/web run build: passed.
+
curl -I http://localhost:3000/tape against the local dev server: returned 200 OK.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+ This change preserves row stability in the frontend view model. It does not alter backend
+ history pagination or wire protocols. The fixed table substrate mitigates visual blanking
+ during fast scrolls, while actual row rendering remains virtualized. Browser automation was
+ attempted, but the local Node automation runtime did not have Playwright installed, so the
+ handoff relies on unit tests, production build, and the local HTTP smoke check.
+
+
+
+
+
Follow-up Work
+
No follow-up Beads issues were needed for this turn.
+
+
+
+
From 37bd393f5c12e4b222dbc1d23bd523df6e5a67fd Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sun, 17 May 2026 06:41:00 -0400
Subject: [PATCH 025/107] Configure beads Dolt remote on Forgejo
---
.beads/config.yaml | 3 +
.beads/issues.jsonl | 2 +
...026-05-17-configure-beads-dolt-remote.html | 193 ++++++++++++++++++
3 files changed, 198 insertions(+)
create mode 100644 docs/turns/2026-05-17-configure-beads-dolt-remote.html
diff --git a/.beads/config.yaml b/.beads/config.yaml
index 232b151..bdf6ede 100644
--- a/.beads/config.yaml
+++ b/.beads/config.yaml
@@ -52,3 +52,6 @@
# - linear.api-key
# - github.org
# - github.repo
+
+sync:
+ remote: git+https://git.deltaisland.io/dirtydishes/islandflow.git
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index eb38e91..4f18056 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,5 +1,6 @@
{"_type":"issue","id":"islandflow-9dg","title":"Fix live tape scroll stability","description":"Live tape rows can shift while a user is scrolled away from the hot head because newer live prints and ClickHouse history are merged into the displayed segment. Implement held-history freezing so only truly older rows append below the current tail, resync on jump-to-top, and tune virtualization/background rendering to reduce fast-scroll blank gaps.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T07:28:52Z","created_by":"dirtydishes","updated_at":"2026-05-17T07:32:53Z","started_at":"2026-05-17T07:29:00Z","closed_at":"2026-05-17T07:32:53Z","close_reason":"Implemented held live tape history freezing, older-only held history append, jump-to-top resync behavior, virtualizer overscan tuning, and stable row-lane table background. Validated with scoped Bun tests, web production build, and local /tape HTTP smoke check.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-qso","title":"Fix durable options tape history routing","description":"Implement the fix-tape plan: make same-origin history routing durable, add deployment/public smoke checks for required API routes, expose tape history loading failures in the UI, document the work, and track api.flow.deltaisland.io migration separately.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T01:53:22Z","created_by":"dirtydishes","updated_at":"2026-05-17T02:00:04Z","started_at":"2026-05-17T01:53:25Z","closed_at":"2026-05-17T02:00:04Z","close_reason":"Implemented durable same-origin history routing, public route smoke checks, tape history diagnostics, docs, validation, and follow-up tracking for api.flow.deltaisland.io.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-8kj","title":"Configure persistent beads Dolt remote on deltaisland server","description":"Install the beads and Dolt CLIs on the server, configure a persistent Dolt sync remote backed by the server-hosted Forgejo repository, verify refs/dolt/data publication, and document Nginx Proxy Manager / firewall considerations.","status":"closed","priority":1,"issue_type":"task","assignee":"delta","created_at":"2026-05-17T10:31:31Z","created_by":"delta","updated_at":"2026-05-17T10:37:47Z","started_at":"2026-05-17T10:32:16Z","closed_at":"2026-05-17T10:37:47Z","close_reason":"Installed bd and dolt on the server, configured the Forgejo-backed Dolt remote, published refs/dolt/data, and documented the setup.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-200","title":"Implement durable options tape history","description":"Implement the plan from docs/plans/2026-05-16-1711-durable-options-tape-history.html: durable ClickHouse-backed options history, signal/all prints view selection, preserved execution context, stale semantics limited to live health, reset runbook, tests, and turn documentation.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:21:30Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:26:51Z","started_at":"2026-05-16T21:21:33Z","closed_at":"2026-05-16T21:26:51Z","close_reason":"Implemented durable options tape history, signal/raw view selection, reset runbook, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-k4f","title":"Gate deploy script on docker workspace snapshot sync","description":"Prevent frozen-lockfile build failures during deploy by adding a local preflight in scripts/deploy.ts that runs bun run check:docker-workspace and aborts with a clear sync+commit remediation message when stale.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:01:44Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:04:11Z","started_at":"2026-05-15T23:01:48Z","closed_at":"2026-05-15T23:04:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-xll","title":"Fix bun.lock drift causing frozen-lockfile Docker build failures","description":"Docker image builds fail in multiple targets (candles, web, ingest services) because bun install --frozen-lockfile detects lockfile changes. Update workspace lockfile to match manifests and verify frozen install succeeds.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T22:52:38Z","created_by":"dirtydishes","updated_at":"2026-05-15T22:55:23Z","started_at":"2026-05-15T22:52:40Z","closed_at":"2026-05-15T22:55:23Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
@@ -43,5 +44,6 @@
{"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-cnk","title":"Run Docker image build verification with active Docker daemon","description":"Targeted image builds could not run in the implementation session because the local Docker daemon was unavailable at unix:///Users/kell/.orbstack/run/docker.sock. When Docker or OrbStack is running, validate the refactored deployment Dockerfiles with: docker compose -f deployment/docker/docker-compose.yml build api; docker compose -f deployment/docker/docker-compose.yml build web; docker compose -f deployment/docker/docker-compose.yml build ingest-options.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:53:41Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:53:41Z","dependencies":[{"issue_id":"islandflow-cnk","depends_on_id":"islandflow-09a","type":"discovered-from","created_at":"2026-05-16T17:53:40Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-zsy","title":"Expose Forgejo SSH on a direct DNS hostname","description":"git.deltaisland.io currently resolves through Cloudflare's proxy, so SSH on port 2222 does not complete even though the Forgejo container is listening on the host. If SSH-based git/beads workflows are desired, add a DNS-only hostname (or adjust the existing record) that points directly at the server for Forgejo SSH.","status":"open","priority":3,"issue_type":"task","created_at":"2026-05-17T10:34:06Z","created_by":"delta","updated_at":"2026-05-17T10:34:06Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-38p","title":"Add native deployment unit templates and rollback helpers","description":"The deploy helper now supports --runtime native, but the repo still relies on operator-managed systemd units and manual rollback. Add checked-in native deployment templates or provisioning guidance for the expected units, and consider lightweight rollback/smoke-test helpers once the host-native path is exercised on the real VPS.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:46:42Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-575","title":"Document smart-money event calendar env","description":"Document smart-money event-calendar environment configuration in env examples and README.\n","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T06:57:14Z","created_by":"dirtydishes","updated_at":"2026-05-05T06:57:57Z","started_at":"2026-05-05T06:57:17Z","closed_at":"2026-05-05T06:57:57Z","close_reason":"Documented event-calendar env variables","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/docs/turns/2026-05-17-configure-beads-dolt-remote.html b/docs/turns/2026-05-17-configure-beads-dolt-remote.html
new file mode 100644
index 0000000..7e401cc
--- /dev/null
+++ b/docs/turns/2026-05-17-configure-beads-dolt-remote.html
@@ -0,0 +1,193 @@
+
+
+
+
+
+ Turn Document - Configure Beads Dolt Remote
+
+
+
+
+
+
Configure Beads Dolt Remote
+
+ Configured a persistent beads/Dolt sync remote for this repo using the server-hosted Forgejo instance at
+ git.deltaisland.io, published Dolt data to refs/dolt/data, and documented the
+ operational constraints around Nginx Proxy Manager, HTTPS, and SSH reachability.
+
+ The repo now has a committed beads sync target in .beads/config.yaml and the server now has the
+ required local tooling and credentials to run bd dolt push successfully against Forgejo over HTTPS.
+
+
+
+
+
Changes Made
+
+
Installed bd 1.0.4 for the delta user.
+
Installed dolt 2.0.3 in ~/.local/bin.
+
Configured a persistent local Forgejo credential for non-interactive beads/Dolt pushes on this server.
+
Added the public beads sync URL to .beads/config.yaml:
Published the current Dolt history to Forgejo and verified refs/dolt/data exists on the remote.
+
Created a follow-up issue for SSH reachability via DNS/Cloudflare: islandflow-zsy.
+
+
+
+
+
Context
+
+ This repo already used beads locally, but it had no Dolt remote configured. Earlier work in the repo had
+ explicitly noted that bd dolt pull was unavailable because no remote existed.
+
+
+ The server already hosted Forgejo behind Nginx Proxy Manager at git.deltaisland.io, which made an
+ HTTPS-backed beads remote the lowest-friction persistent option.
+
+
+
+
+
Important Implementation Details
+
+
+ The public remote URL for collaborators is:
+ git+https://git.deltaisland.io/dirtydishes/islandflow.git
+
+
+ The actual server-side push path is authenticated locally with a Forgejo personal access token stored only on
+ the server, so the committed repo configuration does not contain secrets.
+
+
+ The Nginx Proxy Manager host for git.deltaisland.io already proxies Forgejo on ports 80/443, so no
+ new public port exposure was needed for the working HTTPS path.
+
+
+ A dedicated Forgejo SSH key was also prepared on the server, but end-to-end SSH to git.deltaisland.io:2222
+ is still blocked by the current DNS/proxy setup rather than by the host listener itself.
+
+
+
+
+
+
Expected Impact for End-Users
+
+
Future clones can bootstrap beads from the server-backed remote instead of starting with an empty local database.
+
Operators can now run bd dolt push on this server without manual one-off setup.
+
Beads issue history is now backed by a persistent remote rather than being local-only state.
+
+
+
+
+
Validation
+
+
bd version → 1.0.4
+
dolt version → 2.0.3
+
bd dolt push completed successfully.
+
git ls-remote https://git.deltaisland.io/dirtydishes/islandflow.git refs/dolt/data returned a ref.
+
ss -tulpn confirmed listeners on 80, 443, and 2222.
+
Inspected the Nginx Proxy Manager config for git.deltaisland.io and confirmed HTTPS proxying to the Forgejo container.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
+ SSH hostname reachability: Forgejo is listening on host port 2222, but the
+ current public hostname resolves through a proxy path that does not complete SSH connections. HTTPS remains the
+ supported path today.
+
+
+ Server-local credential material: a local Forgejo token was required so this server can push
+ beads data non-interactively. The secret was kept out of tracked repo files.
+
+
+ Pre-existing repo dirtiness: unrelated local changes already existed in this working tree and
+ were intentionally left untouched.
+
+
+
+
+
+
Follow-up Work
+
+
islandflow-zsy — expose Forgejo SSH on a direct DNS hostname if SSH-based Git/beads sync should work publicly.
+
If additional machines need write access, create Forgejo credentials or PATs for those operators and use the public HTTPS remote above.
+
+
+
+
+
From 0416194df55e46675811b2c0d4f460cca030ab8a Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sun, 17 May 2026 10:05:40 -0400
Subject: [PATCH 026/107] Add standup summary for 2026-05-16 activity
---
.beads/issues.jsonl | 6 +-
...2026-05-17-standup-summary-2026-05-16.html | 493 ++++++++++++++++++
2 files changed, 496 insertions(+), 3 deletions(-)
create mode 100644 docs/general/2026-05-17-standup-summary-2026-05-16.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 4f18056..4fdd8f8 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -14,8 +14,8 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-qd7","title":"Migrate production web to api.flow.deltaisland.io","description":"Follow-up from the durable options tape history fix. Plan and migrate production from same-origin API path proxying on flow.deltaisland.io to a dedicated api.flow.deltaisland.io origin, including DNS, proxy config, CORS/websocket behavior, deployment docs, and public smoke checks.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-17T01:55:02Z","created_by":"dirtydishes","updated_at":"2026-05-17T01:55:02Z","dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-09a","title":"Speed up Docker deployment builds","description":"Implement the Docker deployment optimization plan from /Users/kell/Desktop/speed-up-docker.md: split dependency installation from source copy, add BuildKit caches, make scoped deploys build only their target services, update Docker deployment docs, validate, document the turn, commit, and push.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:50:24Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:53:48Z","started_at":"2026-05-16T21:50:37Z","closed_at":"2026-05-16T21:53:48Z","close_reason":"Implemented Docker dependency-layer caching, scoped deploy build/up flow, Docker docs updates, validation, and turn documentation. Follow-up islandflow-cnk tracks daemon-backed image build verification.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-lyt","title":"Summarize 2026-05-16 git activity for standup","description":"Create a grounded standup summary for yesterday's git activity, anchored to commits, changed files, and any linked PR context if present. Produce the required HTML document in docs/general and complete the beads + git handoff workflow.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T14:02:57Z","created_by":"dirtydishes","updated_at":"2026-05-17T14:05:37Z","started_at":"2026-05-17T14:03:09Z","closed_at":"2026-05-17T14:05:37Z","close_reason":"Created docs/general standup summary for 2026-05-16 git activity, grounded to commits and changed files, and prepared the repo handoff workflow.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-sz8","title":"Fix public /replay/options proxy regression","description":"## Summary\nThe new deploy-time public route checker added in commit 1424a27 (\"fix durable options history routing\") currently fails against https://flow.deltaisland.io because GET /replay/options returns HTML instead of JSON.\n\n## Evidence\n- `bun run scripts/check-public-api-routes.ts https://flow.deltaisland.io` fails on `/replay/options?view=signal\u0026after_ts=0\u0026after_seq=0\u0026limit=1` with `returned non-JSON content (text/html; charset=UTF-8)`\n- `services/api/src/index.ts` implements `GET /replay/options`, so the HTML response indicates the request is landing on the web app instead of the API service\n- `deployment/docker/README.md` documents that same-origin proxy mode must include `/replay/*` in the API route matcher\n\n## Minimal Fix\nUpdate the live reverse proxy / edge route matcher for flow.deltaisland.io so `/replay/*` is forwarded to the API host, then rerun `bun run check:public-api-routes`.\n\n## Notes\nThis looks like a production proxy configuration regression rather than an in-repo application bug.","status":"open","priority":2,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-05-17T13:06:11Z","created_by":"dirtydishes","updated_at":"2026-05-17T13:06:11Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","notes":"Implemented live scroll-hold with no live pause button, demand-loaded ClickHouse history, a 100-row options hot head, and cache-first scoped snapshots. Validated with bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts and bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:23:43Z","started_at":"2026-05-16T18:12:54Z","closed_at":"2026-05-16T18:23:43Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-c87","title":"Clean up duplicate Islandflow Docker infra on VPS","description":"The live VPS is currently running both the production-style islandflow-vps Docker stack and an older root-level islandflow infra stack that publishes NATS, ClickHouse, and Redis on host ports. Investigate whether the older stack is unused, remove it safely if so, and update docs/deploy guidance so the server topology is clearer.","notes":"Inspected the live VPS and confirmed the duplicate compose project: islandflow-vps is the supported deployment stack, while a separate islandflow project from the repo-root docker-compose.yml still runs exposed NATS/ClickHouse/Redis containers. Verified Nginx Proxy Manager routes only to islandflow-vps web/api by Docker name. Attempted cleanup via docker compose down and docker rm -f on the stale islandflow containers, but those commands hung for the delta user and timed out. Added repo guardrails and docs so deploy warns when the duplicate project exists, and opened islandflow-2db for manual host-level cleanup during a maintenance window.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:16:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:07Z","started_at":"2026-05-16T01:16:09Z","closed_at":"2026-05-16T01:28:07Z","close_reason":"Completed the repo-side investigation and guardrails. Actual server-side container removal is blocked by hanging Docker operations and is tracked separately in islandflow-2db for a maintenance window.","dependency_count":0,"dependent_count":0,"comment_count":0}
@@ -33,7 +33,7 @@
{"_type":"issue","id":"islandflow-dod","title":"Publish terminal audit to GitHub Pages","description":"Why this issue exists and what needs to be done: publish the generated terminal audit HTML to dirtydishes.github.io at /terminal-audit.html so it can be shared publicly.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T08:39:45Z","created_by":"dirtydishes","updated_at":"2026-05-14T08:42:59Z","started_at":"2026-05-14T08:40:02Z","closed_at":"2026-05-14T08:42:59Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-dxu","title":"Document terminal audit findings as HTML","description":"Why this issue exists and what needs to be done: capture the completed terminal view audit findings in a user-readable HTML document under docs/ with the full score summary and all detailed findings preserved.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T08:32:22Z","created_by":"dirtydishes","updated_at":"2026-05-14T08:34:57Z","started_at":"2026-05-14T08:32:30Z","closed_at":"2026-05-14T08:34:57Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-a50","title":"Add HTML plan docs for synthetic tape redesign","description":"Create two HTML planning docs under plans/: one straightforward end-user readable version and one more polished impeccable-style version, both covering the hosted synthetic tape redesign with summary, scope, affected services, UI notes, rollout, tests, and the full detailed implementation plan.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T02:47:44Z","created_by":"dirtydishes","updated_at":"2026-05-14T02:53:11Z","started_at":"2026-05-14T02:47:48Z","closed_at":"2026-05-14T02:53:11Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-932","title":"Desktop follow-up native features","description":"Track deferred native desktop features after the thin hosted-wrapper v1 lands: notifications, keyboard shortcuts, local preferences storage, remembered window state, signed/notarized macOS distribution, auto-update evaluation, and optional local frontend bundling.\n","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-13T13:20:12Z","created_by":"dirtydishes","updated_at":"2026-05-13T13:20:12Z","dependencies":[{"issue_id":"islandflow-932","depends_on_id":"islandflow-9ug","type":"discovered-from","created_at":"2026-05-13T09:20:12Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-932","title":"Desktop follow-up native features","description":"Track deferred native desktop features after the thin hosted-wrapper v1 lands: notifications, keyboard shortcuts, local preferences storage, remembered window state, signed/notarized macOS distribution, auto-update evaluation, and optional local frontend bundling.\n","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-13T13:20:12Z","created_by":"dirtydishes","updated_at":"2026-05-13T13:20:12Z","dependencies":[{"issue_id":"islandflow-932","depends_on_id":"islandflow-9ug","type":"discovered-from","created_at":"2026-05-13T09:20:12Z","created_by":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-vbk","title":"Remove deprecated Alpaca key-pair auth","description":"Remove legacy Alpaca key-pair authentication support and keep ALPACA_API_KEY as the only supported auth method across options/equities ingest and docs.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:19:51Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:21:10Z","started_at":"2026-05-05T07:19:54Z","closed_at":"2026-05-05T07:21:10Z","close_reason":"Removed key-pair auth and kept ALPACA_API_KEY only","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-h47","title":"Support single-token Alpaca auth","description":"Support single-token Alpaca authentication across ingest adapters using ALPACA_API_KEY with fallback to ALPACA_KEY_ID/ALPACA_SECRET_KEY, and document env usage.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:12:22Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:13:54Z","started_at":"2026-05-05T07:12:25Z","closed_at":"2026-05-05T07:13:54Z","close_reason":"Added ALPACA_API_KEY support with key-pair fallback","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-neu","title":"Add Alpha Vantage event calendar provider","description":"Add an Alpha Vantage earnings-calendar provider to services/refdata that fetches CSV, normalizes entries, writes the JSON cache consumed by compute, and documents the required env variables.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:00:31Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:02:30Z","started_at":"2026-05-05T07:00:37Z","closed_at":"2026-05-05T07:02:30Z","close_reason":"Added Alpha Vantage event-calendar provider","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/docs/general/2026-05-17-standup-summary-2026-05-16.html b/docs/general/2026-05-17-standup-summary-2026-05-16.html
new file mode 100644
index 0000000..51c50a7
--- /dev/null
+++ b/docs/general/2026-05-17-standup-summary-2026-05-16.html
@@ -0,0 +1,493 @@
+
+
+
+
+
+ Standup Summary for 2026-05-16
+
+
+
+
+
+
Standup Summary
+
Git Activity for Friday, 2026-05-16
+
+ Yesterday's git history shows three product-facing workstreams: live tape behavior fixes,
+ durable options history support, and faster Docker deploy builds. The day also included
+ merge commit f4108b9 for PR #39 and two small AGENTS.md
+ housekeeping updates.
+
+
+
+ Commits
+ 8 commits recorded on 2026-05-16
+
+
+ Author
+ dirtydishes
+
+
+ Primary Areas
+ apps/web, services/api, deployment/docker, scripts
+
+
+ Docs Added
+ 4 turn docs and 1 runbook file
+
+
+
+
+
+
Summary
+
+
+ Live tape behavior was updated in commit 39fb5ce, touching
+ apps/web/app/terminal.tsx and services/api/src/live.ts, with
+ companion test updates in apps/web/app/terminal.test.ts and
+ services/api/tests/live.test.ts.
+
+
+ Durable options history work landed across commits bd60d0d,
+ 2abdd24, and 1424a27, spanning web terminal behavior, API
+ live routing, storage tests, and a new route checker in
+ scripts/check-public-api-routes.ts.
+
+
+ Deploy build performance was adjusted in commit 23ed380 through Dockerfile
+ and deployment script changes under deployment/docker and
+ scripts/deploy.ts.
+
+
+
+
+
+
Changes Made
+
+
+
+
Fix live tape scroll hold and lazy history
+ 39fb5ce
+
+
+
+ Updated live tape behavior in the terminal and API layers, with matching test edits
+ and a turn document added in docs/turns/2026-05-16-live-tape-scroll-hold-history.html.
+
+ Added another round of durable options history work across the terminal UI, API live
+ stream logic, storage tests, and a ClickHouse reset runbook.
+
+ A follow-up implementation commit added .codex/hooks.json and another
+ turn document, followed immediately by merge commit f4108b9 for PR
+ #39 from dirtydishes/options-cache.
+
+ Closed the day with routing fixes for durable options history, including terminal
+ styling updates, deployment script changes, and a new public API route checker.
+
+ Two small commits updated AGENTS.md. One also modified
+ .beads/issues.jsonl.
+
+
+ AGENTS.md
+ .beads/issues.jsonl
+
+
+
+
+
+
+
Context
+
+ This report is derived from git log for the local repository over the full
+ America/New_York day window from 2026-05-16 00:00:00 -0400 through
+ 2026-05-16 23:59:59 -0400. The goal is standup-ready reporting, so the
+ narrative groups related commits together while keeping every statement anchored to a
+ commit, merge, or changed file.
+
+
+ The strongest product-facing cluster is the options history work. It appears in three
+ separate commits plus merge commit f4108b9, and those commits repeatedly touch
+ apps/web/app/terminal.tsx, services/api/src/live.ts, and related tests.
+
+
+
+
+
Important Implementation Details
+
+
+ Commit 39fb5ce paired UI and API changes with test edits in both the web
+ and API packages, which is a useful signal that the live tape behavior change was not
+ isolated to a single layer.
+
+
+ Commit bd60d0d added docs/clickhouse-reset-runbook.md, so the
+ durable options history work included operational documentation alongside code changes.
+
+
+ Commit 23ed380 changed all three Dockerfiles used in deployment plus
+ scripts/deploy.ts, so the build-speed update touched both image definition
+ and deployment orchestration.
+
+
+ Commit 1424a27 introduced scripts/check-public-api-routes.ts,
+ which is the only brand-new script added in yesterday's activity.
+
+
+
+
+
+
Expected Impact for End-Users
+
+
+ Users of the live terminal should see changes connected to tape scroll behavior and
+ history handling because commits 39fb5ce, bd60d0d, and
+ 1424a27 all modified apps/web/app/terminal.tsx.
+
+
+ API consumers and live/replay flows were also part of the day because
+ services/api/src/live.ts changed in two separate commits.
+
+
+ Deployment operators should expect updated Docker build behavior after commit
+ 23ed380 and the later deployment-script follow-up in 1424a27.
+
+
+
+
+
+
Validation
+
+
+ Completed: Git history was queried directly with
+ git log --since='2026-05-16 00:00:00 -0400' --until='2026-05-16 23:59:59 -0400'.
+
+
+ Not run in this reporting task: no fresh
+ bun test, build, or lint commands were executed.
+
+
+ Evidence available in history: yesterday's commits
+ modified test files apps/web/app/terminal.test.ts,
+ services/api/tests/live.test.ts, and
+ packages/storage/tests/option-prints.test.ts.
+
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
+ This summary is limited to local git history for one calendar day, so it does not infer
+ intent beyond what commit subjects, merge text, and changed files support.
+
+
+ PR metadata is only explicitly available for merge commit f4108b9, which
+ names PR #39. Other commits are reported without attaching unverified PR context.
+
+
+ The repo currently has local beads file modifications unrelated to this documenting task,
+ so this report avoids treating current workspace state as part of yesterday's activity.
+
+
+
+
+
+
Follow-up Work
+
+
+ No additional follow-up beads issues were created from the git history itself because
+ the reporting task did not uncover a concrete defect or missing deliverable.
+
+
+ Reporting task tracked in beads issue islandflow-lyt.
+
+
+
+
+
+
From d0d8bd40b996a9469963f77a2225bfab92f4e09b Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sun, 17 May 2026 10:10:03 -0400
Subject: [PATCH 027/107] bd: clear sync.remote
---
.beads/config.yaml | 2 +-
.beads/issues.jsonl | 5 +----
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/.beads/config.yaml b/.beads/config.yaml
index bdf6ede..26a415f 100644
--- a/.beads/config.yaml
+++ b/.beads/config.yaml
@@ -54,4 +54,4 @@
# - github.repo
sync:
- remote: git+https://git.deltaisland.io/dirtydishes/islandflow.git
+ remote: git+https://git.deltaisland.io/dirtydishes/islandflow.git
\ No newline at end of file
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 4fdd8f8..8bb2603 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,5 +1,3 @@
-{"_type":"issue","id":"islandflow-9dg","title":"Fix live tape scroll stability","description":"Live tape rows can shift while a user is scrolled away from the hot head because newer live prints and ClickHouse history are merged into the displayed segment. Implement held-history freezing so only truly older rows append below the current tail, resync on jump-to-top, and tune virtualization/background rendering to reduce fast-scroll blank gaps.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T07:28:52Z","created_by":"dirtydishes","updated_at":"2026-05-17T07:32:53Z","started_at":"2026-05-17T07:29:00Z","closed_at":"2026-05-17T07:32:53Z","close_reason":"Implemented held live tape history freezing, older-only held history append, jump-to-top resync behavior, virtualizer overscan tuning, and stable row-lane table background. Validated with scoped Bun tests, web production build, and local /tape HTTP smoke check.","dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-qso","title":"Fix durable options tape history routing","description":"Implement the fix-tape plan: make same-origin history routing durable, add deployment/public smoke checks for required API routes, expose tape history loading failures in the UI, document the work, and track api.flow.deltaisland.io migration separately.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T01:53:22Z","created_by":"dirtydishes","updated_at":"2026-05-17T02:00:04Z","started_at":"2026-05-17T01:53:25Z","closed_at":"2026-05-17T02:00:04Z","close_reason":"Implemented durable same-origin history routing, public route smoke checks, tape history diagnostics, docs, validation, and follow-up tracking for api.flow.deltaisland.io.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-8kj","title":"Configure persistent beads Dolt remote on deltaisland server","description":"Install the beads and Dolt CLIs on the server, configure a persistent Dolt sync remote backed by the server-hosted Forgejo repository, verify refs/dolt/data publication, and document Nginx Proxy Manager / firewall considerations.","status":"closed","priority":1,"issue_type":"task","assignee":"delta","created_at":"2026-05-17T10:31:31Z","created_by":"delta","updated_at":"2026-05-17T10:37:47Z","started_at":"2026-05-17T10:32:16Z","closed_at":"2026-05-17T10:37:47Z","close_reason":"Installed bd and dolt on the server, configured the Forgejo-backed Dolt remote, published refs/dolt/data, and documented the setup.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-200","title":"Implement durable options tape history","description":"Implement the plan from docs/plans/2026-05-16-1711-durable-options-tape-history.html: durable ClickHouse-backed options history, signal/all prints view selection, preserved execution context, stale semantics limited to live health, reset runbook, tests, and turn documentation.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:21:30Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:26:51Z","started_at":"2026-05-16T21:21:33Z","closed_at":"2026-05-16T21:26:51Z","close_reason":"Implemented durable options tape history, signal/raw view selection, reset runbook, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-k4f","title":"Gate deploy script on docker workspace snapshot sync","description":"Prevent frozen-lockfile build failures during deploy by adding a local preflight in scripts/deploy.ts that runs bun run check:docker-workspace and aborts with a clear sync+commit remediation message when stale.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:01:44Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:04:11Z","started_at":"2026-05-15T23:01:48Z","closed_at":"2026-05-15T23:04:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
@@ -42,8 +40,7 @@
{"_type":"issue","id":"islandflow-020","title":"Rebuild synthetic smart-money scenarios","description":"Rework services/ingest-options synthetic generation around labeled parent-event templates for the six core smart-money profiles plus neutral background noise, with deterministic test/demo modes and hidden labels for tests.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:24Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:29:27Z","started_at":"2026-05-05T05:25:39Z","closed_at":"2026-05-05T05:29:27Z","close_reason":"Completed Phase 5 synthetic smart-money scenario rebuild","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-cnk","title":"Run Docker image build verification with active Docker daemon","description":"Targeted image builds could not run in the implementation session because the local Docker daemon was unavailable at unix:///Users/kell/.orbstack/run/docker.sock. When Docker or OrbStack is running, validate the refactored deployment Dockerfiles with: docker compose -f deployment/docker/docker-compose.yml build api; docker compose -f deployment/docker/docker-compose.yml build web; docker compose -f deployment/docker/docker-compose.yml build ingest-options.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:53:41Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:53:41Z","dependencies":[{"issue_id":"islandflow-cnk","depends_on_id":"islandflow-09a","type":"discovered-from","created_at":"2026-05-16T17:53:40Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-zsy","title":"Expose Forgejo SSH on a direct DNS hostname","description":"git.deltaisland.io currently resolves through Cloudflare's proxy, so SSH on port 2222 does not complete even though the Forgejo container is listening on the host. If SSH-based git/beads workflows are desired, add a DNS-only hostname (or adjust the existing record) that points directly at the server for Forgejo SSH.","status":"open","priority":3,"issue_type":"task","created_at":"2026-05-17T10:34:06Z","created_by":"delta","updated_at":"2026-05-17T10:34:06Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-38p","title":"Add native deployment unit templates and rollback helpers","description":"The deploy helper now supports --runtime native, but the repo still relies on operator-managed systemd units and manual rollback. Add checked-in native deployment templates or provisioning guidance for the expected units, and consider lightweight rollback/smoke-test helpers once the host-native path is exercised on the real VPS.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:46:42Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-575","title":"Document smart-money event calendar env","description":"Document smart-money event-calendar environment configuration in env examples and README.\n","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T06:57:14Z","created_by":"dirtydishes","updated_at":"2026-05-05T06:57:57Z","started_at":"2026-05-05T06:57:17Z","closed_at":"2026-05-05T06:57:57Z","close_reason":"Documented event-calendar env variables","dependency_count":0,"dependent_count":0,"comment_count":0}
From cd0a1dd9e5275e90f5caf8d54295cede387f7fef Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sun, 17 May 2026 10:11:22 -0400
Subject: [PATCH 028/107] bd: update sync.remote
---
.beads/config.yaml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.beads/config.yaml b/.beads/config.yaml
index 26a415f..12fdcdb 100644
--- a/.beads/config.yaml
+++ b/.beads/config.yaml
@@ -54,4 +54,6 @@
# - github.repo
sync:
- remote: git+https://git.deltaisland.io/dirtydishes/islandflow.git
\ No newline at end of file
+ remote: git+https://git.deltaisland.io/dirtydishes/islandflow.git
+
+sync.remote: "git+https://github.com/dirtydishes/islandflow.git"
\ No newline at end of file
From c0b5b6dbeb48282ec55e87fa3126aab4f5e558d3 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sun, 17 May 2026 11:02:30 -0400
Subject: [PATCH 029/107] hydrate alert evidence from clickhouse
---
.beads/issues.jsonl | 1 +
apps/web/app/globals.css | 31 +++
apps/web/app/terminal.test.ts | 40 +++
apps/web/app/terminal.tsx | 229 +++++++++++++-----
...6-05-17-1101-clickhouse-alert-context.html | 194 +++++++++++++++
packages/storage/src/clickhouse.ts | 102 ++++++++
packages/storage/tests/alerts.test.ts | 106 ++++++++
services/api/src/alert-context.ts | 21 ++
services/api/src/index.ts | 21 ++
services/api/tests/alert-context.test.ts | 18 ++
10 files changed, 701 insertions(+), 62 deletions(-)
create mode 100644 docs/turns/2026-05-17-1101-clickhouse-alert-context.html
create mode 100644 services/api/src/alert-context.ts
create mode 100644 services/api/tests/alert-context.test.ts
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 8bb2603..b2f3a4a 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,3 +1,4 @@
+{"_type":"issue","id":"islandflow-jbi","title":"Hydrate alert evidence details from ClickHouse","description":"Alert detail drawers need to fetch persisted alert context from ClickHouse by trace id, including linked flow packets, option prints, preserved execution context, and explicit missing refs for UI diagnostics.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T14:55:43Z","created_by":"dirtydishes","updated_at":"2026-05-17T15:01:58Z","started_at":"2026-05-17T14:55:53Z","closed_at":"2026-05-17T15:01:58Z","close_reason":"Implemented ClickHouse-backed alert context hydration across storage, API, terminal drawer, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-8kj","title":"Configure persistent beads Dolt remote on deltaisland server","description":"Install the beads and Dolt CLIs on the server, configure a persistent Dolt sync remote backed by the server-hosted Forgejo repository, verify refs/dolt/data publication, and document Nginx Proxy Manager / firewall considerations.","status":"closed","priority":1,"issue_type":"task","assignee":"delta","created_at":"2026-05-17T10:31:31Z","created_by":"delta","updated_at":"2026-05-17T10:37:47Z","started_at":"2026-05-17T10:32:16Z","closed_at":"2026-05-17T10:37:47Z","close_reason":"Installed bd and dolt on the server, configured the Forgejo-backed Dolt remote, published refs/dolt/data, and documented the setup.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-200","title":"Implement durable options tape history","description":"Implement the plan from docs/plans/2026-05-16-1711-durable-options-tape-history.html: durable ClickHouse-backed options history, signal/all prints view selection, preserved execution context, stale semantics limited to live health, reset runbook, tests, and turn documentation.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:21:30Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:26:51Z","started_at":"2026-05-16T21:21:33Z","closed_at":"2026-05-16T21:26:51Z","close_reason":"Implemented durable options tape history, signal/raw view selection, reset runbook, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-k4f","title":"Gate deploy script on docker workspace snapshot sync","description":"Prevent frozen-lockfile build failures during deploy by adding a local preflight in scripts/deploy.ts that runs bun run check:docker-workspace and aborts with a clear sync+commit remediation message when stale.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:01:44Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:04:11Z","started_at":"2026-05-15T23:01:48Z","closed_at":"2026-05-15T23:04:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css
index 46f20bb..64b6f16 100644
--- a/apps/web/app/globals.css
+++ b/apps/web/app/globals.css
@@ -1818,6 +1818,28 @@ h3 {
gap: 10px;
}
+.drawer-context-loading {
+ padding: 12px 0 2px;
+}
+
+.drawer-skeleton {
+ width: 64%;
+ height: 12px;
+ border-radius: 999px;
+ background: linear-gradient(90deg, var(--bg-soft), rgba(245, 166, 35, 0.14), var(--bg-soft));
+ background-size: 180% 100%;
+ animation: drawer-skeleton 1.2s ease-out infinite;
+}
+
+.drawer-skeleton-wide {
+ width: 100%;
+}
+
+.drawer-evidence-context {
+ margin-top: 8px;
+ color: var(--text-faint);
+}
+
.drawer-row {
padding: 12px 14px;
border-radius: 12px;
@@ -1825,6 +1847,15 @@ h3 {
background: var(--bg-soft);
}
+@keyframes drawer-skeleton {
+ 0% {
+ background-position: 100% 0;
+ }
+ 100% {
+ background-position: -100% 0;
+ }
+}
+
@keyframes pulse {
0% {
transform: scale(1);
diff --git a/apps/web/app/terminal.test.ts b/apps/web/app/terminal.test.ts
index b6214eb..2be3da8 100644
--- a/apps/web/app/terminal.test.ts
+++ b/apps/web/app/terminal.test.ts
@@ -3,9 +3,11 @@ import { getSubscriptionKey as getLiveSubscriptionKey } from "@islandflow/types"
import {
NAV_ITEMS,
appendHistoryTail,
+ buildAlertContextPath,
buildDefaultFlowFilters,
buildOptionTapeQueryParams,
classifierToneForFamily,
+ collectAlertContextEvidence,
composeTapeItems,
deriveAlertDirection,
countActiveFlowFilterGroups,
@@ -95,6 +97,44 @@ describe("pinned evidence pruning", () => {
});
});
+describe("alert context hydration helpers", () => {
+ it("builds the persisted ClickHouse context endpoint path", () => {
+ expect(buildAlertContextPath("alert:large_call/one")).toBe(
+ "/flow/alerts/alert%3Alarge_call%2Fone/context"
+ );
+ });
+
+ it("merges hydrated packets and prints into pinned evidence maps", () => {
+ const packet = {
+ trace_id: "flowpacket:1",
+ id: "flowpacket:1",
+ members: ["print:1"],
+ source_ts: 1,
+ ingest_ts: 2,
+ seq: 1,
+ features: {},
+ join_quality: {}
+ } as any;
+ const print = makeOptionPrint({
+ trace_id: "print:1",
+ execution_nbbo_bid: 1.2,
+ execution_nbbo_ask: 1.3,
+ execution_underlying_spot: 450.05
+ });
+
+ const evidence = collectAlertContextEvidence({
+ alert: makeAlert({ evidence_refs: ["flowpacket:1", "print:1"] }),
+ flow_packets: [packet],
+ option_prints: [print],
+ missing_refs: []
+ });
+
+ expect(evidence.packets.get("flowpacket:1")).toBe(packet);
+ expect(evidence.prints.get("print:1")?.execution_nbbo_bid).toBe(1.2);
+ expect(evidence.prints.get("print:1")?.execution_underlying_spot).toBe(450.05);
+ });
+});
+
describe("live manifest", () => {
it("includes only tape channels on /tape", () => {
const filters = buildDefaultFlowFilters();
diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx
index 0dfc199..e1ee74c 100644
--- a/apps/web/app/terminal.tsx
+++ b/apps/web/app/terminal.tsx
@@ -4604,6 +4604,49 @@ type EvidenceItem =
| { kind: "print"; id: string; print: OptionPrint }
| { kind: "unknown"; id: string };
+type AlertContextBundle = {
+ alert: AlertEvent | null;
+ flow_packets: FlowPacket[];
+ option_prints: OptionPrint[];
+ missing_refs: string[];
+};
+
+type AlertContextStatus = {
+ traceId: string | null;
+ loading: boolean;
+ missingRefs: string[];
+ error: string | null;
+};
+
+export const buildAlertContextPath = (traceId: string): string =>
+ `/flow/alerts/${encodeURIComponent(traceId)}/context`;
+
+export const collectAlertContextEvidence = (
+ bundle: AlertContextBundle
+): {
+ packets: Map;
+ prints: Map;
+} => {
+ const packets = new Map();
+ const prints = new Map();
+
+ for (const packet of bundle.flow_packets) {
+ if (packet.id) {
+ packets.set(packet.id, packet);
+ }
+ if (packet.trace_id) {
+ packets.set(packet.trace_id, packet);
+ }
+ }
+ for (const print of bundle.option_prints) {
+ if (print.trace_id) {
+ prints.set(print.trace_id, print);
+ }
+ }
+
+ return { packets, prints };
+};
+
type DarkEvidenceItem =
| { kind: "join"; id: string; join: EquityPrintJoin }
| { kind: "unknown"; id: string };
@@ -4612,15 +4655,28 @@ type AlertDrawerProps = {
alert: AlertEvent;
flowPacket: FlowPacket | null;
evidence: EvidenceItem[];
+ contextStatus: AlertContextStatus;
onClose: () => void;
};
-const AlertDrawer = ({ alert, flowPacket, evidence, onClose }: AlertDrawerProps) => {
+const formatOptionalMoney = (value: unknown): string | null => {
+ const parsed = parseNumber(value, Number.NaN);
+ return Number.isFinite(parsed) ? `$${formatPrice(parsed)}` : null;
+};
+
+const formatOptionalMs = (value: unknown): string | null => {
+ const parsed = parseNumber(value, Number.NaN);
+ return Number.isFinite(parsed) ? `${Math.round(parsed)}ms` : null;
+};
+
+const AlertDrawer = ({ alert, flowPacket, evidence, contextStatus, onClose }: AlertDrawerProps) => {
const primary = alert.hits[0];
const direction = deriveAlertDirection(alert);
const severity = normalizeAlertSeverity(alert);
const evidencePrints = evidence.filter((item) => item.kind === "print");
const unknownCount = evidence.filter((item) => item.kind === "unknown").length;
+ const isContextLoading = contextStatus.traceId === alert.trace_id && contextStatus.loading;
+ const missingRefs = contextStatus.traceId === alert.trace_id ? contextStatus.missingRefs : [];
return (