From 62aae7087899359c299b726c4df6e3ce41ce1764 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Mon, 18 May 2026 09:05:40 -0400 Subject: [PATCH 01/12] docs(general): add 2026-05-17 standup summary --- .beads/issues.jsonl | 3 + ...2026-05-18-standup-summary-2026-05-17.html | 549 ++++++++++++++++++ 2 files changed, 552 insertions(+) create mode 100644 docs/general/2026-05-18-standup-summary-2026-05-17.html diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index e025c4d..629eb06 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -13,6 +13,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-k8i","title":"Fix duplicate alert context import in API entrypoint","description":"Recent alert-context work introduced a duplicate fetchAlertContextByTraceId import in services/api/src/index.ts, which risks breaking TypeScript compilation and API startup. Remove the duplicate import and validate the affected API/web tests.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T13:01:58Z","created_by":"dirtydishes","updated_at":"2026-05-18T13:03:40Z","started_at":"2026-05-18T13:02:02Z","closed_at":"2026-05-18T13:03:40Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-lk9","title":"Fix PR creation workflow after Forgejo migration","description":"## Why\\nCreating pull requests with fails after the repository moved primary collaboration from GitHub to Forgejo. The current workflow still assumes GitHub GraphQL PR creation semantics, which do not work against the Forgejo remote.\\n\\n## What\\nInvestigate the current PR creation path, identify remaining GitHub-specific assumptions, and update the repo workflow/scripts/docs so contributors can reliably publish branches and open PRs in the Forgejo-based setup.\\n\\n## Acceptance Criteria\\n- The repo no longer instructs contributors to use a broken GitHub-specific PR creation path for Forgejo branches\\n- There is a documented and preferably scripted way to create the equivalent review request against Forgejo\\n- Validation demonstrates the new workflow behaves correctly or clearly documents any remaining platform limitation","status":"in_progress","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T10:26:47Z","created_by":"dirtydishes","updated_at":"2026-05-18T10:26:53Z","started_at":"2026-05-18T10:26:53Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-1ei","title":"Make deploy helper remote-aware for Forgejo","description":"Why: scripts/deploy.ts hardcodes git remote name origin for fetch/pull/push and branch verification, but this repository now uses forgejo/github remotes and may not have an origin remote. What: update deploy.ts to resolve the deploy git remote robustly (Forgejo-aware), use it across local prechecks, branch publish, and remote rollout git operations, and keep behavior explicit in output.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T03:20:12Z","created_by":"dirtydishes","updated_at":"2026-05-18T03:22:39Z","started_at":"2026-05-18T03:20:16Z","closed_at":"2026-05-18T03:22:39Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-xod","title":"Add --fast mode to deploy helper","description":"Why: full main deploys rebuild all images and run full verification, which is slow for routine rollouts. What: add a --fast flag to scripts/deploy.ts with explicit behavior that short-circuits slow steps while preserving basic safety checks; update help text/docs for discoverability.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T02:50:47Z","created_by":"dirtydishes","updated_at":"2026-05-18T02:53:41Z","started_at":"2026-05-18T02:50:50Z","closed_at":"2026-05-18T02:53:41Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-cif","title":"hydrate alert evidence context from clickhouse","description":"Implement alert detail hydration from ClickHouse with a new context endpoint and frontend drawer evidence resolution. Includes storage lookup by alert trace_id/evidence refs, unresolved refs diagnostics, API route GET /flow/alerts/:trace_id/context, terminal evidence hydration + loading states/copy updates, and tests across storage/api/web.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T00:15:55Z","created_by":"dirtydishes","updated_at":"2026-05-18T00:17:38Z","started_at":"2026-05-18T00:16:00Z","closed_at":"2026-05-18T00:17:38Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -46,6 +48,7 @@ {"_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":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-x70","title":"Create 2026-05-17 git standup summary","description":"Why this issue exists and what needs to be done:\\n- Produce the daily automation summary for 2026-05-17 git activity.\\n- Ground statements in commits, PRs, and touched files only.\\n- Create a user-readable HTML document in docs/general and update automation memory.\\n- Complete the Beads sync and git push workflow after documenting the run.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T13:01:43Z","created_by":"dirtydishes","updated_at":"2026-05-18T13:05:37Z","started_at":"2026-05-18T13:01:53Z","closed_at":"2026-05-18T13:05:37Z","close_reason":"Closed","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/general/2026-05-18-standup-summary-2026-05-17.html b/docs/general/2026-05-18-standup-summary-2026-05-17.html new file mode 100644 index 0000000..ba21b1b --- /dev/null +++ b/docs/general/2026-05-18-standup-summary-2026-05-17.html @@ -0,0 +1,549 @@ + + + + + + Standup Summary for 2026-05-17 + + + +
+
+ Git Standup Summary +

Repository activity recorded for 2026-05-17

+

+ Yesterday's git history shows three main themes: frontend and API work to hydrate alert evidence from + ClickHouse, deploy workflow changes in scripts/deploy.ts, and Beads/Dolt remote setup plus + documentation updates. This summary is grounded in the commits, merged PRs, and touched files visible in the + repository history for 2026-05-17. +

+
+
+ Commit Count + 20 commits on 2026-05-17 +
+
+ Merges + 5 pull request merges +
+
+ File Footprint + 22 distinct paths touched +
+
+ Most Revisited + .beads/issues.jsonl, scripts/deploy.ts, apps/web/app/terminal.tsx +
+
+
+ +
+

Summary

+
+
+ Alert context from ClickHouse landed and was merged twice through follow-up PRs. + The core implementation appeared in commit c0b5b6d and merge PR #41 + (3e08955), then was extended in 58e57fa and merged through #43 + (a27d499) and a documentation polish PR #44 (49efc24). +
+
+ Deploy tooling changed in three steps. + The day included an allowlist tightening in 5ddfbfa, a new fast deploy mode in + 75ed6f3, and Forgejo-aware remote resolution in 6e6788b, all centered on + scripts/deploy.ts. +
+
+ Process and reporting work was visible alongside feature work. + Beads Dolt remote configuration was added in 37bd393, revised in d0d8bd4 and + cd0a1dd, and yesterday's prior standup report was added in 0416194. +
+
+
+ +
+

Changes Made

+
+
+
+ Frontend + API + c0b5b6d + 11:02 EDT +
+

Hydrate alert evidence from ClickHouse

+

+ Commit c0b5b6d added ClickHouse-backed alert context across storage, API, tests, and the + terminal UI. The same change set was merged as PR #41 in 3e08955. +

+
+ packages/storage/src/clickhouse.ts + services/api/src/alert-context.ts + services/api/src/index.ts + apps/web/app/terminal.tsx + apps/web/app/terminal.test.ts + packages/storage/tests/alerts.test.ts +
+
+ +
+
+ Deploy workflow + 5ddfbfa + 11:45 EDT +
+

Tighten deploy remote untracked allowlist

+

+ Commit 5ddfbfa, later merged as PR #42 in 8b166a5, narrowed the + remote untracked allowlist in scripts/deploy.ts. Two follow-up documentation commits, + 8631a53 and 219d3fd, recorded and corrected the validation notes for that + change. +

+
+ scripts/deploy.ts + docs/turns/2026-05-17-deploy-allowlist-pr-packaging.html +
+
+ +
+
+ Integration + 58e57fa + 20:18 EDT +
+

Add ClickHouse alert context hydration for alert drawers

+

+ Commit 58e57fa extended the earlier alert-context work, adding drawer-specific hydration in + the web app and API. A merge-conflict resolution commit dc932cf combined this with the + deploy allowlist branch before PR #43 merged in a27d499. +

+
+ apps/web/app/terminal.tsx + packages/storage/src/clickhouse.ts + services/api/src/index.ts + docs/turns/2026-05-17-clickhouse-alert-context.html +
+
+ +
+
+ Deploy workflow + 75ed6f3 + 22:53 EDT +
+

Add fast deploy mode for routine rollouts

+

+ Commit 75ed6f3 added a faster deploy path and updated both deployment readmes. Minutes + later, commit 6e6788b made deploy remote resolution Forgejo-aware, again in + scripts/deploy.ts. +

+
+ scripts/deploy.ts + deployment/docker/README.md + deployment/native/README.md + docs/turns/2026-05-17-add-fast-deploy-mode.html + docs/turns/2026-05-17-forgejo-deploy-remote-resolution.html +
+
+ +
+
+ Repo operations + 37bd393 + 06:41 EDT +
+

Beads remote setup and daily reporting

+

+ Commit 37bd393 configured the Beads Dolt remote in .beads/config.yaml, then + commits d0d8bd4 and cd0a1dd revised the same sync settings. Commit + 0416194 added the standup summary document for 2026-05-16 activity in + docs/general. +

+
+ .beads/config.yaml + .beads/issues.jsonl + docs/general/2026-05-17-standup-summary-2026-05-16.html +
+
+
+
+ +
+

Context

+
+
+ Merged PRs +
    +
  • #40 merged in 88b2c33: live tape scroll stability and related deploy/image work.
  • +
  • #41 merged in 3e08955: initial ClickHouse alert evidence hydration.
  • +
  • #42 merged in 8b166a5: deploy allowlist packaging follow-through.
  • +
  • #43 merged in a27d499: alert drawer hydration follow-up.
  • +
  • #44 merged in 49efc24: turn-document polish for alert context.
  • +
+
+
+ Most Touched Areas +
    +
  • .beads/issues.jsonl changed in 9 commits, reflecting issue tracking churn throughout the day.
  • +
  • scripts/deploy.ts changed in 3 direct commits tied to deploy safety and speed.
  • +
  • apps/web/app/terminal.tsx changed in 3 direct commits tied to live tape behavior and alert context.
  • +
  • Documentation output expanded across docs/turns and docs/general alongside implementation work.
  • +
+
+
+
+ +
+

Important Implementation Details

+
    +
  • + The ClickHouse alert-context work was not isolated to one layer. Commits c0b5b6d and + 58e57fa touched storage access, API wiring, UI presentation, and dedicated tests, which + makes this the clearest full-stack change in yesterday's history. +
  • +
  • + The deploy changes were incremental rather than a single rewrite. The history shows a narrowing change in + 5ddfbfa, an operator-speed path in 75ed6f3, and remote detection logic in + 6e6788b. +
  • +
  • + Merge commit dc932cf explicitly resolved conflicts between the alert-context and deploy + allowlist branches before later PR merges landed, so yesterday's main branch activity included integration + work as well as feature work. +
  • +
  • + Commit 073c1de created an empty forgejo.test path. The git history shows the file + creation, but no test content in that commit. +
  • +
+
+ +
+

Expected Impact for End-Users

+
    +
  • + User-facing terminal behavior changed in two visible ways: live tape scroll stability from + d334e16/#40 and richer alert evidence context from c0b5b6d, + 58e57fa, and the follow-up merges. +
  • +
  • + Deploy workflow commits affected operator tooling rather than customer-facing product screens. Those changes + should matter most to maintainers using scripts/deploy.ts and the deployment readmes. +
  • +
  • + Beads remote configuration and standup-report commits affected internal workflow and documentation, not + runtime product behavior. +
  • +
+
+ +
+

Validation

+
    +
  • + The turn document added in d334e16 records + bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts passing and + bun --cwd=apps/web run build passing. +
  • +
  • + The turn document added in c0b5b6d records + bun test packages/storage/tests, + bun test services/api/tests, + bun test apps/web/app/terminal.test.ts, and + bun --cwd=apps/web run build. +
  • +
  • + The polished turn document merged in 49efc24 records those alert-context validations as + passing. +
  • +
  • + The deploy allowlist turn document created in 8631a53 and corrected in 219d3fd + explicitly notes that a repository-wide bun test run reported failures at that point. +
  • +
  • + Later deploy-related turn documents added in 75ed6f3 and 6e6788b record full + bun test passing, with the Forgejo remote document stating 232 passing, + 0 failing. +
  • +
  • + This automation run only created documentation. No additional code validation command was run for this + summary itself. +
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+
    +
  • + This document summarizes repository history only. It does not infer goals beyond what commit subjects, PR + titles, merge structure, and touched files show. +
  • +
  • + Some PR context is visible only through merge commits. For example, PR #40 bundles scroll + stability with deploy and Docker-path changes, so the summary reports the merged file footprint rather than + inferring which portion dominated the review. +
  • +
  • + Validation evidence comes from committed turn documents, not from re-running every historical command during + this automation. +
  • +
+
+ +
+

Follow-up Work

+

+ No new follow-up Beads issue was created from the git summary itself. The Beads task for this automation run + is islandflow-x70, which tracks creation of this standup document and will be closed as part of + the session sync. +

+
+
+ + From 906fe411c9daffde038285c3a29f02d07c351e6c Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Mon, 18 May 2026 16:55:31 -0400 Subject: [PATCH 02/12] add alpaca news wire across ingest api and web --- .beads/issues.jsonl | 1 + apps/web/app/globals.css | 74 +++++ apps/web/app/news/page.tsx | 7 + apps/web/app/terminal.test.ts | 21 +- apps/web/app/terminal.tsx | 305 +++++++++++++++++++- bun.lock | 13 + deployment/docker/Dockerfile.ingest-options | 1 + deployment/docker/Dockerfile.service | 1 + deployment/docker/Dockerfile.web | 1 + deployment/docker/docker-compose.yml | 4 + deployment/docker/workspace-root/bun.lock | 13 + docs/turns/2026-05-18-news-wire-view.html | 152 ++++++++++ packages/bus/src/streams.ts | 5 +- packages/bus/src/subjects.ts | 2 + packages/storage/src/clickhouse.ts | 141 +++++++++ packages/storage/src/index.ts | 1 + packages/storage/src/news.ts | 102 +++++++ packages/storage/tests/news.test.ts | 78 +++++ packages/types/src/events.ts | 23 ++ packages/types/src/live.ts | 8 +- packages/types/tests/live.test.ts | 26 +- scripts/deploy.ts | 18 +- scripts/dev-services.ts | 1 + scripts/dev.ts | 1 + services/api/src/index.ts | 54 +++- services/api/src/live.ts | 65 +++-- services/ingest-news/package.json | 16 + services/ingest-news/src/index.ts | 216 ++++++++++++++ services/ingest-news/src/symbols.ts | 70 +++++ services/ingest-news/tests/symbols.test.ts | 30 ++ services/ingest-news/tsconfig.json | 7 + 31 files changed, 1407 insertions(+), 50 deletions(-) create mode 100644 apps/web/app/news/page.tsx create mode 100644 docs/turns/2026-05-18-news-wire-view.html create mode 100644 packages/storage/src/news.ts create mode 100644 packages/storage/tests/news.test.ts create mode 100644 services/ingest-news/package.json create mode 100644 services/ingest-news/src/index.ts create mode 100644 services/ingest-news/src/symbols.ts create mode 100644 services/ingest-news/tests/symbols.test.ts create mode 100644 services/ingest-news/tsconfig.json diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 629eb06..9909cdd 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -13,6 +13,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-8fn","title":"implement alpaca-backed news wire view","description":"Why this issue exists and what needs to be done:\\nAdd an Alpaca-powered live news pipeline, API, storage, and web experience, including a dedicated /news route, Home preview, live fanout, history pagination, ticker resolution, and replay-mode live-only empty states.\\n\\nAcceptance criteria:\\n- normalized NewsStory contract and live channel exist\\n- ingest-news service backfills and streams Alpaca news\\n- API persists, serves, and fans out news\\n- web app exposes /news plus Home preview and drawer\\n- tests cover types, storage, API, and key UI behaviors\\n- turn documentation is added\\n\\nDesign:\\nReuse Islandflow drawer, chips, panes, and terminal styling; keep news live-only in v1 replay mode.\\n\\nNotes:\\nImplement client-side ticker filtering in v1 and expose latest revision only per provider+story_id.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T20:37:13Z","created_by":"dirtydishes","updated_at":"2026-05-18T20:55:11Z","started_at":"2026-05-18T20:37:20Z","closed_at":"2026-05-18T20:55:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-k8i","title":"Fix duplicate alert context import in API entrypoint","description":"Recent alert-context work introduced a duplicate fetchAlertContextByTraceId import in services/api/src/index.ts, which risks breaking TypeScript compilation and API startup. Remove the duplicate import and validate the affected API/web tests.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T13:01:58Z","created_by":"dirtydishes","updated_at":"2026-05-18T13:03:40Z","started_at":"2026-05-18T13:02:02Z","closed_at":"2026-05-18T13:03:40Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-lk9","title":"Fix PR creation workflow after Forgejo migration","description":"## Why\\nCreating pull requests with fails after the repository moved primary collaboration from GitHub to Forgejo. The current workflow still assumes GitHub GraphQL PR creation semantics, which do not work against the Forgejo remote.\\n\\n## What\\nInvestigate the current PR creation path, identify remaining GitHub-specific assumptions, and update the repo workflow/scripts/docs so contributors can reliably publish branches and open PRs in the Forgejo-based setup.\\n\\n## Acceptance Criteria\\n- The repo no longer instructs contributors to use a broken GitHub-specific PR creation path for Forgejo branches\\n- There is a documented and preferably scripted way to create the equivalent review request against Forgejo\\n- Validation demonstrates the new workflow behaves correctly or clearly documents any remaining platform limitation","status":"in_progress","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T10:26:47Z","created_by":"dirtydishes","updated_at":"2026-05-18T10:26:53Z","started_at":"2026-05-18T10:26:53Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-1ei","title":"Make deploy helper remote-aware for Forgejo","description":"Why: scripts/deploy.ts hardcodes git remote name origin for fetch/pull/push and branch verification, but this repository now uses forgejo/github remotes and may not have an origin remote. What: update deploy.ts to resolve the deploy git remote robustly (Forgejo-aware), use it across local prechecks, branch publish, and remote rollout git operations, and keep behavior explicit in output.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T03:20:12Z","created_by":"dirtydishes","updated_at":"2026-05-18T03:22:39Z","started_at":"2026-05-18T03:20:16Z","closed_at":"2026-05-18T03:22:39Z","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 64b6f16..cf6746b 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -708,7 +708,12 @@ h3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } +.page-grid-news { + grid-template-columns: minmax(0, 1fr); +} + .page-grid-home > :nth-child(3), +.page-grid-home > :nth-child(4), .page-grid-tape > :nth-child(1), .page-grid-replay > :nth-child(1) { grid-column: 1 / -1; @@ -933,6 +938,7 @@ h3 { } .page-grid-home > :nth-child(3), +.page-grid-home > :nth-child(4), .page-grid-replay > :not(:first-child) { height: clamp(430px, 58vh, 760px); } @@ -1747,6 +1753,72 @@ h3 { gap: 10px; } +.terminal-link-button { + text-decoration: none; +} + +.news-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.news-row { + width: 100%; + display: flex; + flex-direction: column; + gap: 8px; + padding: 14px 16px; + border: 1px solid var(--border); + border-radius: 12px; + background: oklch(0.18 0.012 250 / 0.6); + color: var(--text); + text-align: left; + transition: border-color 150ms ease, background 150ms ease; +} + +.news-row:hover { + border-color: var(--accent-soft); + background: oklch(0.2 0.015 250 / 0.75); +} + +.news-row-head, +.news-row-meta { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + flex-wrap: wrap; +} + +.news-row h3 { + margin: 0; + font-size: 0.96rem; + font-weight: 600; +} + +.news-row-time { + color: var(--text-dim); + font-family: var(--font-mono), monospace; + font-size: 0.78rem; +} + +.news-row-meta { + color: var(--text-dim); + font-size: 0.78rem; +} + +.news-drawer-body a { + color: var(--accent); +} + +.news-drawer-body p, +.news-drawer-body ul, +.news-drawer-body ol, +.news-drawer-body blockquote { + margin: 0 0 12px; +} + .synthetic-status-grid strong, .synthetic-hit-row strong { font-family: var(--font-mono), monospace; @@ -1964,6 +2036,7 @@ h3 { } .page-grid-home > :nth-child(3), + .page-grid-home > :nth-child(4), .page-grid-tape > :nth-child(1), .page-grid-replay > :nth-child(1) { grid-column: auto; @@ -1973,6 +2046,7 @@ h3 { .page-grid-home > :nth-child(1), .page-grid-home > :nth-child(2), .page-grid-home > :nth-child(3), + .page-grid-home > :nth-child(4), .page-grid-signals > .terminal-pane, .page-grid-replay > :not(:first-child), .page-grid-tape > :first-child, diff --git a/apps/web/app/news/page.tsx b/apps/web/app/news/page.tsx new file mode 100644 index 0000000..7e06aa8 --- /dev/null +++ b/apps/web/app/news/page.tsx @@ -0,0 +1,7 @@ +import { NewsRoute } from "../terminal"; + +export const dynamic = "force-dynamic"; + +export default function Page() { + return ; +} diff --git a/apps/web/app/terminal.test.ts b/apps/web/app/terminal.test.ts index 2be3da8..63918f2 100644 --- a/apps/web/app/terminal.test.ts +++ b/apps/web/app/terminal.test.ts @@ -247,6 +247,15 @@ describe("live manifest", () => { ]); }); + it("includes news subscriptions on home and /news", () => { + expect(getLiveManifest("/", "SPY", 60000, buildDefaultFlowFilters()).map((subscription) => subscription.channel)).toContain( + "news" + ); + expect(getLiveManifest("/news", "SPY", 60000, buildDefaultFlowFilters()).map((subscription) => subscription.channel)).toEqual([ + "news" + ]); + }); + it("scopes /charts subscriptions to chart channels only", () => { const channels = getLiveManifest("/charts", "SPY", 60000, buildDefaultFlowFilters()).map( (subscription) => subscription.channel @@ -431,6 +440,13 @@ describe("route feature map", () => { expect(features.equityOverlay).toBe(true); expect(features.alerts).toBe(false); }); + + it("maps /news to the dedicated news pane", () => { + const features = getRouteFeatures("/news"); + expect(features.news).toBe(true); + expect(features.showNewsPane).toBe(true); + expect(features.showAlertsPane).toBe(false); + }); }); describe("fixed tape virtualization config", () => { @@ -461,10 +477,11 @@ describe("dark underlying route dependency helper", () => { }); describe("terminal navigation", () => { - it("exposes only Home and Tape as top-level destinations", () => { + it("exposes Home, Tape, and News as top-level destinations", () => { expect(NAV_ITEMS).toEqual([ { href: "/", label: "Home" }, - { href: "/tape", label: "Tape" } + { href: "/tape", label: "Tape" }, + { href: "/news", label: "News" } ]); }); }); diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx index e1ee74c..218e149 100644 --- a/apps/web/app/terminal.tsx +++ b/apps/web/app/terminal.tsx @@ -33,6 +33,7 @@ import type { LiveServerMessage, LiveHotChannelHealthMap, LiveSubscription, + NewsStory, OptionFlowFilters, OptionFlowView, OptionNbboSide, @@ -158,6 +159,7 @@ type RouteFeatures = { nbbo: boolean; equities: boolean; flow: boolean; + news: boolean; alerts: boolean; smartMoney: boolean; classifierHits: boolean; @@ -168,6 +170,7 @@ type RouteFeatures = { showOptionsPane: boolean; showEquitiesPane: boolean; showFlowPane: boolean; + showNewsPane: boolean; showAlertsPane: boolean; showClassifierPane: boolean; showDarkPane: boolean; @@ -187,6 +190,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { const includeEquitiesFallback = shouldIncludeEquitiesForDarkUnderlyingFallback(); const normalizedPath = pathname === "/tape" || + pathname === "/news" || pathname === "/signals" || pathname === "/charts" || pathname === "/replay" @@ -200,6 +204,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { nbbo: true, equities: true, flow: true, + news: false, alerts: false, smartMoney: false, classifierHits: false, @@ -210,6 +215,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { showOptionsPane: true, showEquitiesPane: true, showFlowPane: true, + showNewsPane: false, showAlertsPane: false, showClassifierPane: false, showDarkPane: false, @@ -220,12 +226,41 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { needsAlertEvidencePrefetch: false, needsDarkUnderlying: false }; + case "/news": + return { + options: false, + nbbo: false, + equities: false, + flow: false, + news: true, + alerts: false, + smartMoney: false, + classifierHits: false, + inferredDark: false, + equityJoins: false, + equityCandles: false, + equityOverlay: false, + showOptionsPane: false, + showEquitiesPane: false, + showFlowPane: false, + showNewsPane: true, + showAlertsPane: false, + showClassifierPane: false, + showDarkPane: false, + showChartPane: false, + showFocusPane: false, + showReplayConsole: false, + needsClassifierDecor: false, + needsAlertEvidencePrefetch: false, + needsDarkUnderlying: false + }; case "/signals": return { options: false, nbbo: false, equities: includeEquitiesFallback, flow: false, + news: false, alerts: true, smartMoney: true, classifierHits: true, @@ -236,6 +271,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { showOptionsPane: false, showEquitiesPane: false, showFlowPane: false, + showNewsPane: false, showAlertsPane: true, showClassifierPane: true, showDarkPane: true, @@ -252,6 +288,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { nbbo: false, equities: includeEquitiesFallback, flow: false, + news: false, alerts: false, smartMoney: true, classifierHits: false, @@ -262,6 +299,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { showOptionsPane: false, showEquitiesPane: false, showFlowPane: false, + showNewsPane: false, showAlertsPane: false, showClassifierPane: false, showDarkPane: false, @@ -278,6 +316,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { nbbo: false, equities: false, flow: false, + news: false, alerts: false, smartMoney: false, classifierHits: false, @@ -288,6 +327,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { showOptionsPane: true, showEquitiesPane: false, showFlowPane: true, + showNewsPane: false, showAlertsPane: true, showClassifierPane: false, showDarkPane: false, @@ -305,6 +345,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { nbbo: false, equities: true, flow: false, + news: true, alerts: true, smartMoney: true, classifierHits: false, @@ -315,6 +356,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => { showOptionsPane: false, showEquitiesPane: true, showFlowPane: false, + showNewsPane: true, showAlertsPane: true, showClassifierPane: false, showDarkPane: false, @@ -332,6 +374,7 @@ const EMPTY_ALERT_EVENTS: AlertEvent[] = []; const EMPTY_CLASSIFIER_HIT_EVENTS: ClassifierHitEvent[] = []; const EMPTY_SMART_MONEY_EVENTS: SmartMoneyEvent[] = []; const EMPTY_INFERRED_DARK_EVENTS: InferredDarkEvent[] = []; +const EMPTY_NEWS_STORIES: NewsStory[] = []; type CandlestickSeries = ReturnType; @@ -1194,6 +1237,44 @@ const formatDateTime = (ts: number): string => { return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`; }; +const isSameLocalDay = (left: number, right: number): boolean => { + const a = new Date(left); + const b = new Date(right); + return ( + a.getFullYear() === b.getFullYear() && + a.getMonth() === b.getMonth() && + a.getDate() === b.getDate() + ); +}; + +export const formatNewsTimestamp = (ts: number, now = Date.now()): string => { + const date = new Date(ts); + return isSameLocalDay(ts, now) + ? date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }) + : date.toLocaleString([], { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }); +}; + +const sanitizeNewsHtml = (value: string): { html: string; fallbackText: string; sanitized: boolean } => { + const fallbackText = value + .replace(//gi, " ") + .replace(//gi, " ") + .replace(/<[^>]+>/g, " ") + .replace(/\s+/g, " ") + .trim(); + + try { + const sanitized = value + .replace(//gi, "") + .replace(//gi, "") + .replace(/\son\w+=(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "") + .replace(/\shref=(["'])javascript:[\s\S]*?\1/gi, ' href="#"') + .replace(/<(?!\/?(p|div|section|article|span|strong|em|b|i|ul|ol|li|br|a|h1|h2|h3|h4|blockquote)\b)[^>]*>/gi, ""); + return { html: sanitized, fallbackText, sanitized: true }; + } catch { + return { html: "", fallbackText, sanitized: false }; + } +}; + const humanizeClassifierId = (value: string): string => { if (!value) { return "Classifier"; @@ -2870,6 +2951,7 @@ type LiveSessionState = { smartMoneyHistory: SmartMoneyEvent[]; classifierHitsHistory: ClassifierHitEvent[]; alertsHistory: AlertEvent[]; + newsHistory: NewsStory[]; inferredDarkHistory: InferredDarkEvent[]; options: OptionPrint[]; nbbo: OptionNBBO[]; @@ -2880,6 +2962,7 @@ type LiveSessionState = { smartMoney: SmartMoneyEvent[]; classifierHits: ClassifierHitEvent[]; alerts: AlertEvent[]; + news: NewsStory[]; inferredDark: InferredDarkEvent[]; chartCandles: EquityCandle[]; chartOverlay: EquityPrint[]; @@ -2900,6 +2983,7 @@ const LIVE_HISTORY_ENDPOINTS: Partial([]); const [classifierHits, setClassifierHits] = useState([]); const [alerts, setAlerts] = useState([]); + const [news, setNews] = useState([]); const [inferredDark, setInferredDark] = useState([]); const [optionsHistory, setOptionsHistory] = useState([]); const [nbboHistory, setNbboHistory] = useState([]); @@ -3142,6 +3230,7 @@ const useLiveSession = ( const [smartMoneyHistory, setSmartMoneyHistory] = useState([]); const [classifierHitsHistory, setClassifierHitsHistory] = useState([]); const [alertsHistory, setAlertsHistory] = useState([]); + const [newsHistory, setNewsHistory] = useState([]); const [inferredDarkHistory, setInferredDarkHistory] = useState([]); const [chartCandles, setChartCandles] = useState([]); const [chartOverlay, setChartOverlay] = useState([]); @@ -3154,6 +3243,7 @@ const useLiveSession = ( const smartMoneyRef = useRef([]); const classifierHitsRef = useRef([]); const alertsRef = useRef([]); + const newsRef = useRef([]); const inferredDarkRef = useRef([]); const chartCandlesRef = useRef([]); const chartOverlayRef = useRef([]); @@ -3165,6 +3255,7 @@ const useLiveSession = ( const smartMoneyHistoryRef = useRef([]); const classifierHitsHistoryRef = useRef([]); const alertsHistoryRef = useRef([]); + const newsHistoryRef = useRef([]); const inferredDarkHistoryRef = useRef([]); const socketRef = useRef(null); const reconnectRef = useRef(null); @@ -3218,6 +3309,7 @@ const useLiveSession = ( setSmartMoney([]); setClassifierHits([]); setAlerts([]); + setNews([]); setInferredDark([]); setOptionsHistory([]); setNbboHistory([]); @@ -3227,6 +3319,7 @@ const useLiveSession = ( setSmartMoneyHistory([]); setClassifierHitsHistory([]); setAlertsHistory([]); + setNewsHistory([]); setInferredDarkHistory([]); setChartCandles([]); setChartOverlay([]); @@ -3239,6 +3332,7 @@ const useLiveSession = ( smartMoneyRef.current = []; classifierHitsRef.current = []; alertsRef.current = []; + newsRef.current = []; inferredDarkRef.current = []; chartCandlesRef.current = []; chartOverlayRef.current = []; @@ -3250,6 +3344,7 @@ const useLiveSession = ( smartMoneyHistoryRef.current = []; classifierHitsHistoryRef.current = []; alertsHistoryRef.current = []; + newsHistoryRef.current = []; inferredDarkHistoryRef.current = []; subscribedKeysRef.current = new Set(); subscribedMapRef.current = new Map(); @@ -3403,6 +3498,12 @@ const useLiveSession = ( ref: alertsHistoryRef }); break; + case "news": + mergeItems(setNews, newsRef, items as NewsStory[], LIVE_OPTIONS_HEAD_LIMIT, { + setter: setNewsHistory, + ref: newsHistoryRef + }); + break; case "inferred-dark": mergeItems(setInferredDark, inferredDarkRef, items as InferredDarkEvent[], LIVE_HOT_WINDOW, { setter: setInferredDarkHistory, @@ -3694,6 +3795,9 @@ const useLiveSession = ( case "alerts": mergeOlder(setAlertsHistory, alertsHistoryRef, alertsRef.current); break; + case "news": + mergeOlder(setNewsHistory, newsHistoryRef, newsRef.current); + break; case "inferred-dark": mergeOlder(setInferredDarkHistory, inferredDarkHistoryRef, inferredDarkRef.current); break; @@ -3735,6 +3839,7 @@ const useLiveSession = ( smartMoneyHistory, classifierHitsHistory, alertsHistory, + newsHistory, inferredDarkHistory, options, nbbo, @@ -3745,6 +3850,7 @@ const useLiveSession = ( smartMoney, classifierHits, alerts, + news, inferredDark, chartCandles, chartOverlay @@ -4822,6 +4928,69 @@ const AlertDrawer = ({ alert, flowPacket, evidence, contextStatus, onClose }: Al ); }; +type NewsDrawerProps = { + story: NewsStory; + onClose: () => void; +}; + +const NewsDrawer = ({ story, onClose }: NewsDrawerProps) => { + const body = sanitizeNewsHtml(story.content_html); + + return ( + + ); +}; + type ClassifierHitDrawerProps = { hit: ClassifierHitEvent; flowPacket: FlowPacket | null; @@ -5178,6 +5347,7 @@ const useTerminalState = () => { const [mode, setMode] = useState("live"); const [replaySource, setReplaySource] = useState(null); const [selectedAlert, setSelectedAlert] = useState(null); + const [selectedNewsStory, setSelectedNewsStory] = useState(null); const [selectedDarkEvent, setSelectedDarkEvent] = useState(null); const [selectedClassifierHit, setSelectedClassifierHit] = useState(null); const [selectedSmartMoneyEvent, setSelectedSmartMoneyEvent] = useState(null); @@ -5274,12 +5444,13 @@ const useTerminalState = () => { }, [mode]); useEffect(() => { - if (!selectedAlert && !selectedClassifierHit && !selectedDarkEvent && !selectedSmartMoneyEvent) { + if (!selectedAlert && !selectedNewsStory && !selectedClassifierHit && !selectedDarkEvent && !selectedSmartMoneyEvent) { return; } const dismissDrawers = () => { setSelectedAlert(null); + setSelectedNewsStory(null); setSelectedClassifierHit(null); setSelectedSmartMoneyEvent(null); setSelectedDarkEvent(null); @@ -5305,7 +5476,7 @@ const useTerminalState = () => { document.removeEventListener("mousedown", handlePointerDown); document.removeEventListener("keydown", handleKeyDown); }; - }, [selectedAlert, selectedClassifierHit, selectedDarkEvent, selectedSmartMoneyEvent]); + }, [selectedAlert, selectedNewsStory, selectedClassifierHit, selectedDarkEvent, selectedSmartMoneyEvent]); const optionsScroll = useListScroll(); const equitiesScroll = useListScroll(); @@ -5540,6 +5711,14 @@ const useTerminalState = () => { ) : equityJoins; const flowFeed = mode === "live" ? liveFlow : flow; + const newsFeed = + mode === "live" + ? toStaticTapeState( + liveSession.status, + composeTapeItems([], liveSession.news, liveSession.newsHistory), + liveSession.lastUpdate + ) + : toStaticTapeState("disconnected", [], null); const alertsFeed = mode === "live" ? toStaticTapeState( @@ -6490,6 +6669,16 @@ const useTerminalState = () => { routeFeatures.needsAlertEvidencePrefetch ]); + const filteredNews = useMemo(() => { + if (!routeFeatures.news && !routeFeatures.showNewsPane) { + return EMPTY_NEWS_STORIES; + } + if (tickerSet.size === 0) { + return newsFeed.items; + } + return newsFeed.items.filter((story) => story.resolved_symbols.some((symbol) => matchesTicker(symbol))); + }, [matchesTicker, newsFeed.items, routeFeatures.news, routeFeatures.showNewsPane, tickerSet]); + const visibleAlerts = useMemo(() => { if (routeFeatures.needsAlertEvidencePrefetch) { return filteredAlerts.slice(0, 12); @@ -6767,6 +6956,7 @@ const useTerminalState = () => { (hit: ClassifierHitEvent) => { const alert = findAlertForClassifierHit(hit); if (alert) { + setSelectedNewsStory(null); setSelectedClassifierHit(null); setSelectedDarkEvent(null); setSelectedSmartMoneyEvent(null); @@ -6774,6 +6964,7 @@ const useTerminalState = () => { return; } + setSelectedNewsStory(null); setSelectedAlert(null); setSelectedDarkEvent(null); setSelectedSmartMoneyEvent(null); @@ -6783,6 +6974,7 @@ const useTerminalState = () => { ); const openFromSmartMoneyEvent = useCallback((event: SmartMoneyEvent) => { + setSelectedNewsStory(null); setSelectedAlert(null); setSelectedClassifierHit(null); setSelectedDarkEvent(null); @@ -6797,6 +6989,7 @@ const useTerminalState = () => { ); const handleDarkMarkerClick = useCallback((event: InferredDarkEvent) => { + setSelectedNewsStory(null); setSelectedAlert(null); setSelectedClassifierHit(null); setSelectedSmartMoneyEvent(null); @@ -6817,6 +7010,9 @@ const useTerminalState = () => { if (routeFeatures.flow || routeFeatures.showFlowPane) { updates.push(flowFeed.lastUpdate); } + if (routeFeatures.news || routeFeatures.showNewsPane) { + updates.push(newsFeed.lastUpdate); + } if (routeFeatures.alerts || routeFeatures.showAlertsPane) { updates.push(alertsFeed.lastUpdate); } @@ -6839,6 +7035,8 @@ const useTerminalState = () => { routeFeatures.showFocusPane, routeFeatures.flow, routeFeatures.showFlowPane, + routeFeatures.news, + routeFeatures.showNewsPane, routeFeatures.alerts, routeFeatures.showAlertsPane, routeFeatures.smartMoney, @@ -6849,6 +7047,7 @@ const useTerminalState = () => { equitiesFeed.lastUpdate, inferredDarkFeed.lastUpdate, flowFeed.lastUpdate, + newsFeed.lastUpdate, alertsFeed.lastUpdate, smartMoneyFeed.lastUpdate, classifierHitsFeed.lastUpdate @@ -6861,6 +7060,8 @@ const useTerminalState = () => { setReplaySource, selectedAlert, setSelectedAlert, + selectedNewsStory, + setSelectedNewsStory, selectedDarkEvent, setSelectedDarkEvent, selectedClassifierHit, @@ -6887,6 +7088,7 @@ const useTerminalState = () => { equityJoins: equityJoinsFeed, nbbo: nbboFeed, inferredDark: inferredDarkFeed, + news: newsFeed, flow: flowFeed, alerts: alertsFeed, smartMoney: smartMoneyFeed, @@ -6920,6 +7122,7 @@ const useTerminalState = () => { equitiesScopedQuiet, equitiesSilentWarning, filteredInferredDark, + filteredNews, filteredFlow, filteredAlerts, filteredSmartMoneyEvents, @@ -6953,7 +7156,8 @@ const useTerminal = (): TerminalState => { export const NAV_ITEMS = [ { href: "/", label: "Home" }, - { href: "/tape", label: "Tape" } + { href: "/tape", label: "Tape" }, + { href: "/news", label: "News" } ] as const; type PageFrameProps = { @@ -7780,6 +7984,7 @@ const AlertsPane = memo(({ state, limit, withStrip = false, className }: AlertsP data-tape-key={key} style={{ transform: `translateY(${start}px)` }} onClick={() => { + state.setSelectedNewsStory(null); state.setSelectedDarkEvent(null); state.setSelectedClassifierHit(null); state.setSelectedSmartMoneyEvent(null); @@ -7806,6 +8011,83 @@ const AlertsPane = memo(({ state, limit, withStrip = false, className }: AlertsP ); }); +type NewsPaneProps = { + state: TerminalState; + limit?: number; + className?: string; +}; + +const NewsPane = memo(({ state, limit, className }: NewsPaneProps) => { + const items = limit ? state.filteredNews.slice(0, limit) : state.filteredNews; + const canLoadOlder = state.mode === "live" && !limit && items.length > 0; + + return ( + + View all + + ) : ( +
+ + {state.mode === "live" ? "Live wire" : "Live-only in v1"} +
+ ) + } + actions={ + canLoadOlder ? ( + + ) : null + } + > + {state.mode === "replay" ? ( +
News is live-only in v1.
+ ) : items.length === 0 ? ( +
+ {state.tickerSet.size > 0 ? "No news stories match the current filter." : "Waiting for live news stories."} +
+ ) : ( +
+ {items.map((story) => ( + + ))} +
+ )} +
+ ); +}); + type ClassifierPaneProps = { state: TerminalState; limit?: number; @@ -8016,6 +8298,7 @@ const DarkPane = memo(({ state, limit, className }: DarkPaneProps) => { data-tape-key={key} style={{ transform: `translateY(${start}px)` }} onClick={() => { + state.setSelectedNewsStory(null); state.setSelectedAlert(null); state.setSelectedClassifierHit(null); state.setSelectedSmartMoneyEvent(null); @@ -8624,6 +8907,10 @@ export function TerminalAppShell({ children }: { children: ReactNode }) { /> ) : null} + {state.selectedNewsStory ? ( + state.setSelectedNewsStory(null)} /> + ) : null} + {state.selectedClassifierHit ? ( + ); } +export function NewsRoute() { + const state = useTerminal(); + return ( + +
+ +
+
+ ); +} + export function TapeRoute() { const state = useTerminal(); return ( diff --git a/bun.lock b/bun.lock index 46160a7..35e00d7 100644 --- a/bun.lock +++ b/bun.lock @@ -121,6 +121,17 @@ "zod": "^3.23.8", }, }, + "services/ingest-news": { + "name": "@islandflow/ingest-news", + "dependencies": { + "@islandflow/bus": "workspace:*", + "@islandflow/config": "workspace:*", + "@islandflow/observability": "workspace:*", + "@islandflow/types": "workspace:*", + "ws": "^8.18.3", + "zod": "^3.23.8", + }, + }, "services/ingest-options": { "name": "@islandflow/ingest-options", "dependencies": { @@ -250,6 +261,8 @@ "@islandflow/ingest-equities": ["@islandflow/ingest-equities@workspace:services/ingest-equities"], + "@islandflow/ingest-news": ["@islandflow/ingest-news@workspace:services/ingest-news"], + "@islandflow/ingest-options": ["@islandflow/ingest-options@workspace:services/ingest-options"], "@islandflow/observability": ["@islandflow/observability@workspace:packages/observability"], diff --git a/deployment/docker/Dockerfile.ingest-options b/deployment/docker/Dockerfile.ingest-options index 52cba59..212b96b 100644 --- a/deployment/docker/Dockerfile.ingest-options +++ b/deployment/docker/Dockerfile.ingest-options @@ -31,6 +31,7 @@ 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-news/package.json ./services/ingest-news/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 diff --git a/deployment/docker/Dockerfile.service b/deployment/docker/Dockerfile.service index e0fcf72..4a7d9f1 100644 --- a/deployment/docker/Dockerfile.service +++ b/deployment/docker/Dockerfile.service @@ -24,6 +24,7 @@ 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-news/package.json ./services/ingest-news/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 diff --git a/deployment/docker/Dockerfile.web b/deployment/docker/Dockerfile.web index 33723ae..37443d9 100644 --- a/deployment/docker/Dockerfile.web +++ b/deployment/docker/Dockerfile.web @@ -30,6 +30,7 @@ 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-news/package.json ./services/ingest-news/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 diff --git a/deployment/docker/docker-compose.yml b/deployment/docker/docker-compose.yml index 96598ba..37682f6 100644 --- a/deployment/docker/docker-compose.yml +++ b/deployment/docker/docker-compose.yml @@ -115,6 +115,10 @@ services: <<: *service-common command: ["services/ingest-equities/src/index.ts"] + ingest-news: + <<: *service-common + command: ["services/ingest-news/src/index.ts"] + replay: <<: *service-common profiles: ["replay"] diff --git a/deployment/docker/workspace-root/bun.lock b/deployment/docker/workspace-root/bun.lock index 46160a7..35e00d7 100644 --- a/deployment/docker/workspace-root/bun.lock +++ b/deployment/docker/workspace-root/bun.lock @@ -121,6 +121,17 @@ "zod": "^3.23.8", }, }, + "services/ingest-news": { + "name": "@islandflow/ingest-news", + "dependencies": { + "@islandflow/bus": "workspace:*", + "@islandflow/config": "workspace:*", + "@islandflow/observability": "workspace:*", + "@islandflow/types": "workspace:*", + "ws": "^8.18.3", + "zod": "^3.23.8", + }, + }, "services/ingest-options": { "name": "@islandflow/ingest-options", "dependencies": { @@ -250,6 +261,8 @@ "@islandflow/ingest-equities": ["@islandflow/ingest-equities@workspace:services/ingest-equities"], + "@islandflow/ingest-news": ["@islandflow/ingest-news@workspace:services/ingest-news"], + "@islandflow/ingest-options": ["@islandflow/ingest-options@workspace:services/ingest-options"], "@islandflow/observability": ["@islandflow/observability@workspace:packages/observability"], diff --git a/docs/turns/2026-05-18-news-wire-view.html b/docs/turns/2026-05-18-news-wire-view.html new file mode 100644 index 0000000..be02f26 --- /dev/null +++ b/docs/turns/2026-05-18-news-wire-view.html @@ -0,0 +1,152 @@ + + + + + + Turn Report: News Wire View via Alpaca Feed + + + +
+

Created 2026-05-18 · Task: News Wire View via Alpaca Feed

+

News Wire View via Alpaca Feed

+
+ Summary +

+ Added an Alpaca-backed live news pipeline end to end: normalized NewsStory types, + a dedicated JetStream subject/stream, ClickHouse storage helpers with latest-revision semantics, + a new services/ingest-news service, API endpoints and live fanout, and a web + /news route plus Home preview with a right-side story drawer. +

+
+ +
+

Changes Made

+
    +
  • Added NewsStorySchema, the news live channel, and subscription parsing support in packages/types.
  • +
  • Added bus constants for the flow.news subject and NEWS stream.
  • +
  • Added ClickHouse news storage helpers, including recent, before-cursor, and after-cursor queries that collapse provider revisions to the latest row per provider + story_id.
  • +
  • Created services/ingest-news with Alpaca REST backfill, Alpaca websocket streaming, normalization, and deterministic ticker resolution.
  • +
  • Extended the API service to persist live news in the shared cache, expose GET /news and GET /history/news, and fan out news events on /ws/live.
  • +
  • Added a top-level /news route, primary nav entry, Home preview pane, replay-mode live-only empty states, and a sanitized full-story drawer.
  • +
  • Updated dev and deployment wiring so the new service is included in local runners and the Docker workspace snapshot.
  • +
+
+ +
+

Context

+

+ The plan called for a free-provider v1 news surface that behaves like the rest of Islandflow: + compact, evidence-first, and live-native. The implementation keeps replay intentionally out of scope + for news while still integrating news into the same live manifest, history pagination, rail navigation, + and drawer language used elsewhere in the terminal. +

+
+ +
+

Important Implementation Details

+
    +
  • Ticker resolution prefers provider symbols first, then falls back only to structured patterns in provider HTML: ticker anchors, EXCHANGE:SYM, and $SYM.
  • +
  • News history uses published_ts as the visible cursor while revisions are collapsed with a window function over provider, story_id ordered by updated_ts, ingest_ts, and seq.
  • +
  • The web drawer sanitizes provider HTML by removing scripts, inline event handlers, and unsupported tags; if sanitization yields nothing useful, the drawer falls back to stripped plain text.
  • +
  • Replay mode intentionally renders a clear empty state for news on both Home and /news instead of pretending news is replay-synced.
  • +
+
resolved_symbols = provider_symbols
+  or ticker anchors in content_html
+  or EXCHANGE:SYM matches
+  or $SYM matches
+
+ +
+

Expected Impact for End-Users

+

+ Traders can now monitor a dedicated live news wire inside Islandflow, spot symbol-linked headlines from + the Home view, and open full stories in-context without leaving the app. The displayed ticker chips are + grounded in stored provider and derived symbol metadata, which makes the feed safer to filter and trust. +

+
+ +
+

Validation

+
    +
  • Ran targeted Bun tests covering types, storage, API live-state behavior, ingest-news symbol resolution, route wiring, and terminal helpers.
  • +
  • Built the Next.js web app with bun --cwd=apps/web run build.
  • +
  • Ran bun run check:docker-workspace after syncing the deployment workspace snapshot.
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+
    +
  • Replay support remains intentionally absent in v1; the UI now states that explicitly instead of showing misleading empty historical behavior.
  • +
  • The sanitizer is intentionally conservative and custom, which keeps dependencies light but may strip some harmless provider formatting.
  • +
  • The ingest service assumes Alpaca’s current REST and websocket news contracts; if Alpaca changes those payload shapes, the normalization layer will need adjustment.
  • +
+
+ +
+

Follow-up Work

+
    +
  • No additional follow-up issue was required during this turn.
  • +
  • Future extensions are still available behind the same contract: multi-provider aggregation, server-side symbol filtering, and replay-aware news history.
  • +
+
+
+ + diff --git a/packages/bus/src/streams.ts b/packages/bus/src/streams.ts index eeb8116..b23c125 100644 --- a/packages/bus/src/streams.ts +++ b/packages/bus/src/streams.ts @@ -7,6 +7,7 @@ import { STREAM_EQUITY_QUOTES, STREAM_FLOW_PACKETS, STREAM_INFERRED_DARK, + STREAM_NEWS, STREAM_OPTION_NBBO, STREAM_OPTION_PRINTS, STREAM_OPTION_SIGNAL_PRINTS, @@ -19,6 +20,7 @@ import { SUBJECT_EQUITY_QUOTES, SUBJECT_FLOW_PACKETS, SUBJECT_INFERRED_DARK, + SUBJECT_NEWS, SUBJECT_OPTION_NBBO, SUBJECT_OPTION_PRINTS, SUBJECT_OPTION_SIGNAL_PRINTS, @@ -53,7 +55,8 @@ export const STREAM_CATALOG: readonly KnownStreamDefinition[] = [ retentionClass: "derived" }, { name: STREAM_CLASSIFIER_HITS, subject: SUBJECT_CLASSIFIER_HITS, retentionClass: "derived" }, - { name: STREAM_ALERTS, subject: SUBJECT_ALERTS, retentionClass: "derived" } + { name: STREAM_ALERTS, subject: SUBJECT_ALERTS, retentionClass: "derived" }, + { name: STREAM_NEWS, subject: SUBJECT_NEWS, retentionClass: "derived" } ]; const STREAM_CATALOG_BY_NAME = new Map(STREAM_CATALOG.map((definition) => [definition.name, definition])); diff --git a/packages/bus/src/subjects.ts b/packages/bus/src/subjects.ts index 6b21afd..956d357 100644 --- a/packages/bus/src/subjects.ts +++ b/packages/bus/src/subjects.ts @@ -22,3 +22,5 @@ export const STREAM_CLASSIFIER_HITS = "CLASSIFIER_HITS"; export const SUBJECT_CLASSIFIER_HITS = "flow.classifier_hits"; export const STREAM_ALERTS = "ALERTS"; export const SUBJECT_ALERTS = "flow.alerts"; +export const STREAM_NEWS = "NEWS"; +export const SUBJECT_NEWS = "flow.news"; diff --git a/packages/storage/src/clickhouse.ts b/packages/storage/src/clickhouse.ts index bc0061e..af469d7 100644 --- a/packages/storage/src/clickhouse.ts +++ b/packages/storage/src/clickhouse.ts @@ -7,6 +7,7 @@ import { EquityPrintJoinSchema, InferredDarkEventSchema, FlowPacketSchema, + NewsStorySchema, OptionNBBOSchema, OptionPrintSchema, SmartMoneyEventSchema @@ -20,6 +21,7 @@ import type { EquityPrintJoin, InferredDarkEvent, FlowPacket, + NewsStory, SmartMoneyEvent, OptionNBBO, OptionPrint, @@ -91,6 +93,13 @@ import { toSmartMoneyEventRecord, type SmartMoneyEventRecord } from "./smart-money-events"; +import { + NEWS_TABLE, + newsTableDDL, + fromNewsRecord, + toNewsRecord, + type NewsRecord +} from "./news"; export type ClickHouseOptions = { url: string; @@ -320,6 +329,12 @@ export const ensureAlertsTable = async (client: ClickHouseClient): Promise } }; +export const ensureNewsTable = async (client: ClickHouseClient): Promise => { + await client.exec({ + query: newsTableDDL() + }); +}; + export const insertOptionPrint = async ( client: ClickHouseClient, print: OptionPrint @@ -449,6 +464,15 @@ export const insertAlert = async (client: ClickHouseClient, alert: AlertEvent): }); }; +export const insertNewsStory = async (client: ClickHouseClient, story: NewsStory): Promise => { + const record = toNewsRecord(story); + await client.insert({ + table: NEWS_TABLE, + values: [record], + format: "JSONEachRow" + }); +}; + export type ClickHouseBatchWriterOptions = { flushIntervalMs?: number; maxRows?: number; @@ -600,6 +624,13 @@ export const enqueueAlertInsert = ( writer.enqueue(ALERTS_TABLE, toAlertRecord(alert)); }; +export const enqueueNewsStoryInsert = ( + writer: ClickHouseBatchWriter, + story: NewsStory +): void => { + writer.enqueue(NEWS_TABLE, toNewsRecord(story)); +}; + const clampLimit = (limit: number): number => { if (!Number.isFinite(limit)) { return 100; @@ -1016,6 +1047,32 @@ const normalizeAlertRow = (row: unknown): AlertRecord | null => { }; }; +const normalizeNewsRow = (row: unknown): NewsRecord | null => { + if (!row || typeof row !== "object") { + return null; + } + + const record = row as Record; + return { + source_ts: coerceNumber(record.source_ts) as number, + ingest_ts: coerceNumber(record.ingest_ts) as number, + seq: coerceNumber(record.seq) as number, + trace_id: String(record.trace_id ?? ""), + story_id: coerceNumber(record.story_id) as number, + provider: String(record.provider ?? ""), + source: String(record.source ?? ""), + headline: String(record.headline ?? ""), + summary: String(record.summary ?? ""), + content_html: String(record.content_html ?? ""), + url: String(record.url ?? ""), + published_ts: coerceNumber(record.published_ts) as number, + updated_ts: coerceNumber(record.updated_ts) as number, + provider_symbols_json: String(record.provider_symbols_json ?? "[]"), + resolved_symbols_json: String(record.resolved_symbols_json ?? "[]"), + symbol_resolution: String(record.symbol_resolution ?? "none") as NewsRecord["symbol_resolution"] + }; +}; + export const fetchRecentOptionPrints = async ( client: ClickHouseClient, limit: number, @@ -1207,6 +1264,50 @@ export const fetchRecentAlerts = async ( return AlertEventSchema.array().parse(alerts); }; +const latestNewsSelect = ` +SELECT + source_ts, + ingest_ts, + seq, + trace_id, + story_id, + provider, + source, + headline, + summary, + content_html, + url, + published_ts, + updated_ts, + provider_symbols_json, + resolved_symbols_json, + symbol_resolution +FROM ( + SELECT + *, + row_number() OVER (PARTITION BY provider, story_id ORDER BY updated_ts DESC, ingest_ts DESC, seq DESC) AS revision_rank + FROM ${NEWS_TABLE} +) +WHERE revision_rank = 1 +`; + +export const fetchRecentNews = async ( + client: ClickHouseClient, + limit: number +): Promise => { + const safeLimit = clampLimit(limit); + const result = await client.query({ + query: `${latestNewsSelect} ORDER BY published_ts DESC, story_id DESC LIMIT ${safeLimit}`, + format: "JSONEachRow" + }); + + const rows = await result.json(); + const records = rows + .map(normalizeNewsRow) + .filter((record): record is NewsRecord => record !== null); + return NewsStorySchema.array().parse(records.map(fromNewsRecord)); +}; + const normalizeAlertEvidenceRefs = (refs: string[]): string[] => { return Array.from(new Set(refs.map((ref) => ref.trim()).filter(Boolean))); }; @@ -1600,6 +1701,27 @@ export const fetchAlertsAfter = async ( return AlertEventSchema.array().parse(alerts); }; +export const fetchNewsAfter = async ( + client: ClickHouseClient, + afterTs: number, + afterSeq: number, + limit: number +): Promise => { + const safeLimit = clampLimit(limit); + const safeAfterTs = clampCursor(afterTs); + const safeAfterSeq = clampCursor(afterSeq); + const result = await client.query({ + query: `${latestNewsSelect} AND (published_ts, seq) > (${safeAfterTs}, ${safeAfterSeq}) ORDER BY published_ts ASC, seq ASC LIMIT ${safeLimit}`, + format: "JSONEachRow" + }); + + const rows = await result.json(); + const records = rows + .map(normalizeNewsRow) + .filter((record): record is NewsRecord => record !== null); + return NewsStorySchema.array().parse(records.map(fromNewsRecord)); +}; + export const fetchOptionPrintsBefore = async ( client: ClickHouseClient, beforeTs: number, @@ -1778,6 +1900,25 @@ export const fetchAlertsBefore = async ( return AlertEventSchema.array().parse(records.map(fromAlertRecord)); }; +export const fetchNewsBefore = async ( + client: ClickHouseClient, + beforeTs: number, + beforeSeq: number, + limit: number +): Promise => { + const safeLimit = clampLimit(limit); + const result = await client.query({ + query: `${latestNewsSelect} AND ${buildBeforeTupleCondition("published_ts", "seq", beforeTs, beforeSeq)} ORDER BY published_ts DESC, seq DESC LIMIT ${safeLimit}`, + format: "JSONEachRow" + }); + + const rows = await result.json(); + const records = rows + .map(normalizeNewsRow) + .filter((record): record is NewsRecord => record !== null); + return NewsStorySchema.array().parse(records.map(fromNewsRecord)); +}; + export const fetchInferredDarkBefore = async ( client: ClickHouseClient, beforeTs: number, diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts index 4fefabc..810d67c 100644 --- a/packages/storage/src/index.ts +++ b/packages/storage/src/index.ts @@ -10,3 +10,4 @@ export * from "./equity-print-joins"; export * from "./inferred-dark"; export * from "./option-prints"; export * from "./option-nbbo"; +export * from "./news"; diff --git a/packages/storage/src/news.ts b/packages/storage/src/news.ts new file mode 100644 index 0000000..cf92f40 --- /dev/null +++ b/packages/storage/src/news.ts @@ -0,0 +1,102 @@ +import type { NewsStory, NewsSymbolResolution } from "@islandflow/types"; + +export const NEWS_TABLE = "news"; + +export type NewsRecord = { + source_ts: number; + ingest_ts: number; + seq: number; + trace_id: string; + story_id: number; + provider: string; + source: string; + headline: string; + summary: string; + content_html: string; + url: string; + published_ts: number; + updated_ts: number; + provider_symbols_json: string; + resolved_symbols_json: string; + symbol_resolution: NewsSymbolResolution; +}; + +export const newsTableDDL = (): string => { + return ` +CREATE TABLE IF NOT EXISTS ${NEWS_TABLE} ( + source_ts UInt64, + ingest_ts UInt64, + seq UInt64, + trace_id String, + story_id UInt64, + provider String, + source String, + headline String, + summary String, + content_html String, + url String, + published_ts UInt64, + updated_ts UInt64, + provider_symbols_json String, + resolved_symbols_json String, + symbol_resolution String +) +ENGINE = ReplacingMergeTree(updated_ts) +ORDER BY (provider, story_id, updated_ts, seq) +`; +}; + +const safeStringArray = (value: string): string[] => { + try { + const parsed = JSON.parse(value); + if (Array.isArray(parsed)) { + return parsed.map((entry) => String(entry)); + } + } catch { + // ignore + } + + return []; +}; + +export const toNewsRecord = (story: NewsStory): NewsRecord => { + return { + source_ts: story.source_ts, + ingest_ts: story.ingest_ts, + seq: story.seq, + trace_id: story.trace_id, + story_id: story.story_id, + provider: story.provider, + source: story.source, + headline: story.headline, + summary: story.summary, + content_html: story.content_html, + url: story.url, + published_ts: story.published_ts, + updated_ts: story.updated_ts, + provider_symbols_json: JSON.stringify(story.provider_symbols), + resolved_symbols_json: JSON.stringify(story.resolved_symbols), + symbol_resolution: story.symbol_resolution + }; +}; + +export const fromNewsRecord = (record: NewsRecord): NewsStory => { + return { + source_ts: record.source_ts, + ingest_ts: record.ingest_ts, + seq: record.seq, + trace_id: record.trace_id, + story_id: record.story_id, + provider: record.provider, + source: record.source, + headline: record.headline, + summary: record.summary, + content_html: record.content_html, + url: record.url, + published_ts: record.published_ts, + updated_ts: record.updated_ts, + provider_symbols: safeStringArray(record.provider_symbols_json), + resolved_symbols: safeStringArray(record.resolved_symbols_json), + symbol_resolution: record.symbol_resolution + }; +}; diff --git a/packages/storage/tests/news.test.ts b/packages/storage/tests/news.test.ts new file mode 100644 index 0000000..c5b71c8 --- /dev/null +++ b/packages/storage/tests/news.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from "bun:test"; +import type { ClickHouseClient } from "../src/clickhouse"; +import { + NEWS_TABLE, + fromNewsRecord, + newsTableDDL, + toNewsRecord +} from "../src/news"; +import { + fetchNewsAfter, + fetchNewsBefore, + fetchRecentNews +} from "../src/clickhouse"; + +const makeClient = (resolver: (query: string) => unknown[]): ClickHouseClient => + ({ + exec: async () => {}, + insert: async () => {}, + ping: async () => ({ success: true }), + close: async () => {}, + query: async ({ query }: { query: string }) => ({ + async json() { + return resolver(query) as T; + } + }) + }) as ClickHouseClient; + +const story = { + source_ts: 100, + ingest_ts: 101, + seq: 3, + trace_id: "alpaca:77", + story_id: 77, + provider: "alpaca", + source: "Benzinga", + headline: "TSLA rises", + summary: "Summary", + content_html: "

TSLA rises

", + url: "https://example.com/story", + published_ts: 100, + updated_ts: 120, + provider_symbols: ["TSLA"], + resolved_symbols: ["TSLA", "AAPL"], + symbol_resolution: "mixed" as const +}; + +describe("news storage helpers", () => { + it("includes the correct table name in the DDL", () => { + const ddl = newsTableDDL(); + expect(ddl).toContain(NEWS_TABLE); + expect(ddl).toContain("ReplacingMergeTree"); + }); + + it("round-trips news records", () => { + const record = toNewsRecord(story); + const restored = fromNewsRecord(record); + expect(restored).toEqual(story); + }); + + it("uses latest-revision selection for recent and cursor queries", async () => { + const queries: string[] = []; + const client = makeClient((query) => { + queries.push(query); + return [toNewsRecord(story)]; + }); + + const recent = await fetchRecentNews(client, 10); + const before = await fetchNewsBefore(client, 200, 10, 10); + const after = await fetchNewsAfter(client, 50, 1, 10); + + expect(recent[0]?.trace_id).toBe("alpaca:77"); + expect(before[0]?.story_id).toBe(77); + expect(after[0]?.updated_ts).toBe(120); + expect(queries[0]).toContain("row_number() OVER"); + expect(queries[1]).toContain("published_ts"); + expect(queries[2]).toContain("(published_ts, seq) > (50, 1)"); + }); +}); diff --git a/packages/types/src/events.ts b/packages/types/src/events.ts index c15dc7b..0556bd8 100644 --- a/packages/types/src/events.ts +++ b/packages/types/src/events.ts @@ -262,3 +262,26 @@ export const InferredDarkEventSchema = EventMetaSchema.merge( ); export type InferredDarkEvent = z.infer; + +export const NewsSymbolResolutionSchema = z.enum(["provider", "derived", "mixed", "none"]); + +export type NewsSymbolResolution = z.infer; + +export const NewsStorySchema = EventMetaSchema.merge( + z.object({ + story_id: z.number().int().nonnegative(), + provider: z.string().min(1), + source: z.string().min(1), + headline: z.string().min(1), + summary: z.string(), + content_html: z.string(), + url: z.string().url().or(z.literal("")), + published_ts: z.number().int().nonnegative(), + updated_ts: z.number().int().nonnegative(), + provider_symbols: z.array(z.string().min(1)), + resolved_symbols: z.array(z.string().min(1)), + symbol_resolution: NewsSymbolResolutionSchema + }) +); + +export type NewsStory = z.infer; diff --git a/packages/types/src/live.ts b/packages/types/src/live.ts index 0787c84..10ac486 100644 --- a/packages/types/src/live.ts +++ b/packages/types/src/live.ts @@ -8,6 +8,7 @@ import { EquityQuoteSchema, FlowPacketSchema, InferredDarkEventSchema, + NewsStorySchema, OptionNBBOSchema, OptionPrintSchema, SmartMoneyEventSchema @@ -34,7 +35,8 @@ export const LiveGenericChannelSchema = z.enum([ "smart-money", "classifier-hits", "alerts", - "inferred-dark" + "inferred-dark", + "news" ]); export const LiveChannelSchema = z.enum([ @@ -48,6 +50,7 @@ export const LiveChannelSchema = z.enum([ "classifier-hits", "alerts", "inferred-dark", + "news", "equity-candles", "equity-overlay" ]); @@ -91,7 +94,7 @@ export const LiveSubscriptionSchema = z.discriminatedUnion("channel", [ snapshot_limit: z.number().int().positive().optional() }), z.object({ - channel: z.enum(["nbbo", "equity-quotes", "equity-joins", "classifier-hits", "alerts", "inferred-dark"]), + channel: z.enum(["nbbo", "equity-quotes", "equity-joins", "classifier-hits", "alerts", "inferred-dark", "news"]), snapshot_limit: z.number().int().positive().optional() }), z.object({ @@ -123,6 +126,7 @@ const livePayloadSchemas = { "classifier-hits": ClassifierHitEventSchema, alerts: AlertEventSchema, "inferred-dark": InferredDarkEventSchema, + news: NewsStorySchema, "equity-candles": EquityCandleSchema, "equity-overlay": EquityPrintSchema } as const; diff --git a/packages/types/tests/live.test.ts b/packages/types/tests/live.test.ts index 075eab1..ef254b4 100644 --- a/packages/types/tests/live.test.ts +++ b/packages/types/tests/live.test.ts @@ -9,6 +9,7 @@ import { describe("live protocol types", () => { it("builds stable keys for generic and parameterized subscriptions", () => { expect(getSubscriptionKey({ channel: "flow" })).toBe("flow|{}"); + expect(getSubscriptionKey({ channel: "news" })).toBe("news"); expect( getSubscriptionKey({ channel: "options", @@ -53,12 +54,13 @@ describe("live protocol types", () => { op: "subscribe", subscriptions: [ { channel: "flow", filters: { nbboSides: ["AA", "A"], minNotional: 50000 } }, + { channel: "news", snapshot_limit: 100 }, { channel: "equity-candles", underlying_id: "SPY", interval_ms: 60000 } ] }); expect(parsed.op).toBe("subscribe"); - expect(parsed.subscriptions).toHaveLength(2); + expect(parsed.subscriptions).toHaveLength(3); }); it("validates snapshot and event server messages", () => { @@ -74,18 +76,24 @@ describe("live protocol types", () => { }); const event = LiveServerMessageSchema.parse({ op: "event", - subscription: { channel: "equity-overlay", underlying_id: "SPY" }, + subscription: { channel: "news" }, item: { source_ts: 100, ingest_ts: 101, seq: 1, - trace_id: "eq-1", - ts: 100, - underlying_id: "SPY", - price: 500, - size: 10, - exchange: "X", - offExchangeFlag: true + trace_id: "alpaca:1", + story_id: 1, + provider: "alpaca", + source: "Benzinga", + headline: "TSLA rises", + summary: "", + content_html: "

TSLA rises

", + url: "https://example.com/story", + published_ts: 100, + updated_ts: 100, + provider_symbols: ["TSLA"], + resolved_symbols: ["TSLA"], + symbol_resolution: "provider" }, watermark: cursor }); diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 68d260a..5b94d95 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -48,7 +48,8 @@ const NATIVE_UNITS = { ingestOptions: process.env.DEPLOY_NATIVE_INGEST_OPTIONS_UNIT?.trim() || "islandflow-ingest-options", ingestEquities: - process.env.DEPLOY_NATIVE_INGEST_EQUITIES_UNIT?.trim() || "islandflow-ingest-equities" + process.env.DEPLOY_NATIVE_INGEST_EQUITIES_UNIT?.trim() || "islandflow-ingest-equities", + ingestNews: process.env.DEPLOY_NATIVE_INGEST_NEWS_UNIT?.trim() || "islandflow-ingest-news" } as const; const DOCKER_CORE_SERVICES = [ "api", @@ -56,14 +57,16 @@ const DOCKER_CORE_SERVICES = [ "compute", "candles", "ingest-options", - "ingest-equities" + "ingest-equities", + "ingest-news" ] as const; const DOCKER_BACKEND_SERVICES = [ "api", "compute", "candles", "ingest-options", - "ingest-equities" + "ingest-equities", + "ingest-news" ] as const; const scriptPath = fileURLToPath(import.meta.url); @@ -106,7 +109,8 @@ Environment: 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.`); + DEPLOY_NATIVE_INGEST_EQUITIES_UNIT Override native ingest-equities systemd unit name. + DEPLOY_NATIVE_INGEST_NEWS_UNIT Override native ingest-news systemd unit name.`); process.exit(exitCode); } @@ -465,7 +469,8 @@ function nativeUnitsForScope(scope: DeployScope): string[] { NATIVE_UNITS.compute, NATIVE_UNITS.candles, NATIVE_UNITS.ingestOptions, - NATIVE_UNITS.ingestEquities + NATIVE_UNITS.ingestEquities, + NATIVE_UNITS.ingestNews ]; default: return [ @@ -474,7 +479,8 @@ function nativeUnitsForScope(scope: DeployScope): string[] { NATIVE_UNITS.compute, NATIVE_UNITS.candles, NATIVE_UNITS.ingestOptions, - NATIVE_UNITS.ingestEquities + NATIVE_UNITS.ingestEquities, + NATIVE_UNITS.ingestNews ]; } } diff --git a/scripts/dev-services.ts b/scripts/dev-services.ts index 09cd381..2bcb641 100644 --- a/scripts/dev-services.ts +++ b/scripts/dev-services.ts @@ -222,6 +222,7 @@ process.on("SIGHUP", () => handleSignal("SIGHUP")); const tasks: ChildSpec[] = [ { name: "ingest-options", cmd: ["bun", "run", "dev"], cwd: "services/ingest-options" }, { name: "ingest-equities", cmd: ["bun", "run", "dev"], cwd: "services/ingest-equities" }, + { name: "ingest-news", cmd: ["bun", "run", "dev"], cwd: "services/ingest-news" }, { name: "compute", cmd: ["bun", "run", "dev"], cwd: "services/compute" }, { name: "candles", cmd: ["bun", "run", "dev"], cwd: "services/candles" }, { name: "refdata", cmd: ["bun", "run", "dev"], cwd: "services/refdata" }, diff --git a/scripts/dev.ts b/scripts/dev.ts index 64406d6..1d031a7 100644 --- a/scripts/dev.ts +++ b/scripts/dev.ts @@ -325,6 +325,7 @@ const serviceTasks: ChildSpec[] = [ { name: "web", cmd: ["bun", "run", "dev"], cwd: "apps/web" }, { name: "ingest-options", cmd: ["bun", "run", "dev"], cwd: "services/ingest-options" }, { name: "ingest-equities", cmd: ["bun", "run", "dev"], cwd: "services/ingest-equities" }, + { name: "ingest-news", cmd: ["bun", "run", "dev"], cwd: "services/ingest-news" }, { name: "compute", cmd: ["bun", "run", "dev"], cwd: "services/compute" }, { name: "candles", cmd: ["bun", "run", "dev"], cwd: "services/candles" }, { name: "refdata", cmd: ["bun", "run", "dev"], cwd: "services/refdata" }, diff --git a/services/api/src/index.ts b/services/api/src/index.ts index 433222a..c0a9c79 100644 --- a/services/api/src/index.ts +++ b/services/api/src/index.ts @@ -9,6 +9,7 @@ import { SUBJECT_EQUITY_QUOTES, SUBJECT_INFERRED_DARK, SUBJECT_FLOW_PACKETS, + SUBJECT_NEWS, SUBJECT_SMART_MONEY_EVENTS, SUBJECT_OPTION_NBBO, SUBJECT_OPTION_SIGNAL_PRINTS, @@ -20,6 +21,7 @@ import { STREAM_EQUITY_QUOTES, STREAM_INFERRED_DARK, STREAM_FLOW_PACKETS, + STREAM_NEWS, STREAM_SMART_MONEY_EVENTS, STREAM_OPTION_NBBO, STREAM_OPTION_SIGNAL_PRINTS, @@ -35,6 +37,7 @@ import { import { createClickHouseClient, ensureAlertsTable, + ensureNewsTable, ensureClassifierHitsTable, ensureEquityCandlesTable, ensureEquityPrintJoinsTable, @@ -48,6 +51,8 @@ import { fetchAlertsAfter, fetchAlertsBefore, fetchAlertContextByTraceId, + fetchNewsAfter, + fetchNewsBefore, fetchClassifierHitsAfter, fetchClassifierHitsBefore, fetchSmartMoneyEventsAfter, @@ -58,6 +63,7 @@ import { fetchFlowPacketsByMemberTraceIds, fetchFlowPacketsBefore, fetchRecentAlerts, + fetchRecentNews, fetchRecentClassifierHits, fetchRecentSmartMoneyEvents, fetchRecentEquityPrintJoins, @@ -99,6 +105,7 @@ import { EquityQuoteSchema, FeedSnapshot, InferredDarkEventSchema, + NewsStorySchema, LiveClientMessageSchema, LiveServerMessage, LiveSubscription, @@ -676,7 +683,8 @@ const run = async () => { STREAM_FLOW_PACKETS, STREAM_SMART_MONEY_EVENTS, STREAM_CLASSIFIER_HITS, - STREAM_ALERTS + STREAM_ALERTS, + STREAM_NEWS ], { logger } ); @@ -719,6 +727,7 @@ const run = async () => { await ensureSmartMoneyEventsTable(clickhouse); await ensureClassifierHitsTable(clickhouse); await ensureAlertsTable(clickhouse); + await ensureNewsTable(clickhouse); }); let redis: ReturnType | null = null; @@ -843,6 +852,11 @@ const run = async () => { subject: SUBJECT_ALERTS, stream: STREAM_ALERTS, durableName: "api-alerts" + }, + { + subject: SUBJECT_NEWS, + stream: STREAM_NEWS, + durableName: "api-news" } ] as const; @@ -991,10 +1005,16 @@ const run = async () => { consumerBindings[10].durableName ); + const newsSubscription = await subscribeWithReset( + consumerBindings[11].subject, + consumerBindings[11].stream, + consumerBindings[11].durableName + ); + const fanoutLive = async ( subscription: LiveSubscription, item: unknown, - ingestChannel: "options" | "nbbo" | "equities" | "equity-quotes" | "equity-candles" | "equity-overlay" | "equity-joins" | "flow" | "classifier-hits" | "alerts" | "inferred-dark" + ingestChannel: "options" | "nbbo" | "equities" | "equity-quotes" | "equity-candles" | "equity-overlay" | "equity-joins" | "flow" | "classifier-hits" | "alerts" | "inferred-dark" | "news" ) => { const watermark = await liveState.ingest(ingestChannel, item); @@ -1252,6 +1272,21 @@ const run = async () => { } }; + const pumpNews = async () => { + for await (const msg of newsSubscription.messages) { + try { + const payload = NewsStorySchema.parse(newsSubscription.decode(msg)); + await fanoutLive({ channel: "news" }, payload, "news"); + msg.ack(); + } catch (error) { + logger.error("failed to process news story", { + error: error instanceof Error ? error.message : String(error) + }); + msg.term(); + } + } + }; + void pumpOptions(); void pumpOptionNbbo(); void pumpEquities(); @@ -1263,6 +1298,7 @@ const run = async () => { void pumpSmartMoney(); void pumpClassifierHits(); void pumpAlerts(); + void pumpNews(); const buildSyntheticStatusBody = () => { const derived = @@ -1490,6 +1526,12 @@ const run = async () => { return jsonResponse({ data }); } + if (req.method === "GET" && url.pathname === "/news") { + const limit = parseLimit(url.searchParams.get("limit") ?? "100"); + const data = await fetchRecentNews(clickhouse, limit); + return jsonResponse({ data }); + } + if (req.method === "GET" && isAlertContextPath(url.pathname)) { try { const traceId = parseAlertContextTraceIdPath(url.pathname); @@ -1607,6 +1649,14 @@ const run = async () => { ); } + if (req.method === "GET" && url.pathname === "/history/news") { + const { beforeTs, beforeSeq, limit } = parseBeforeParams(url); + const data = await fetchNewsBefore(clickhouse, beforeTs, beforeSeq, limit); + return jsonResponse( + buildHistoryResponse(data, (item) => ({ ts: item.published_ts, seq: item.seq })) + ); + } + if (req.method === "GET" && /^\/flow\/packets\/[^/]+$/.test(url.pathname)) { const id = decodeURIComponent(url.pathname.slice("/flow/packets/".length)); const data = await fetchFlowPacketById(clickhouse, id); diff --git a/services/api/src/live.ts b/services/api/src/live.ts index 024935e..c8d2886 100644 --- a/services/api/src/live.ts +++ b/services/api/src/live.ts @@ -8,6 +8,7 @@ import { fetchRecentEquityQuotes, fetchRecentFlowPackets, fetchRecentInferredDark, + fetchRecentNews, fetchRecentOptionNBBO, fetchRecentSmartMoneyEvents, type ClickHouseClient @@ -25,6 +26,7 @@ import { FeedSnapshot, FlowPacketSchema, InferredDarkEventSchema, + NewsStorySchema, LiveChannelHealth, LiveGenericChannel, LiveHotChannel, @@ -40,6 +42,7 @@ import { type EquityCandle, type EquityPrint, type LiveChannel, + type NewsStory, type OptionPrint } from "@islandflow/types"; import { createMetrics } from "@islandflow/observability"; @@ -63,7 +66,8 @@ const GENERIC_LIMIT_ENV_KEYS: Record = { "smart-money": "LIVE_LIMIT_SMART_MONEY", "classifier-hits": "LIVE_LIMIT_CLASSIFIER_HITS", alerts: "LIVE_LIMIT_ALERTS", - "inferred-dark": "LIVE_LIMIT_INFERRED_DARK" + "inferred-dark": "LIVE_LIMIT_INFERRED_DARK", + news: "LIVE_LIMIT_NEWS" }; const CHART_LIMITS = { @@ -81,7 +85,8 @@ const DEFAULT_LIVE_LIMITS: GenericLiveLimits = { "smart-money": 300, "classifier-hits": 300, alerts: 300, - "inferred-dark": 300 + "inferred-dark": 300, + news: 100 }; const DEFAULT_SCOPED_CACHE_MAX_KEYS = 32; @@ -196,16 +201,28 @@ export const resolveGenericLiveLimits = (env: NodeJS.ProcessEnv = process.env): env, "inferred-dark", env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS["inferred-dark"] - ) + ), + news: parseGenericLimit(env, "news", env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.news) }; }; -const parsePositiveInt = (value: string | undefined, fallback: number): number => { - const parsed = Number(value); - if (!Number.isFinite(parsed)) { - return fallback; +const extractFreshnessTs = (channel: LiveGenericChannel, item: any): number | null => { + switch (channel) { + case "options": + case "nbbo": + case "equities": + case "equity-quotes": + return typeof item.ts === "number" ? item.ts : null; + case "flow": + case "classifier-hits": + case "alerts": + case "inferred-dark": + return typeof item.source_ts === "number" ? item.source_ts : null; + case "news": + return typeof item.published_ts === "number" ? item.published_ts : null; + default: + return null; } - return Math.max(1, Math.floor(parsed)); }; export const resolveLiveStateConfig = (env: NodeJS.ProcessEnv = process.env): LiveStateConfig => ({ @@ -217,6 +234,13 @@ export const resolveLiveStateConfig = (env: NodeJS.ProcessEnv = process.env): Li ), redisFlushMaxItems: parsePositiveInt(env.LIVE_REDIS_FLUSH_MAX_ITEMS, DEFAULT_REDIS_FLUSH_MAX_ITEMS) }); +const parsePositiveInt = (value: string | undefined, fallback: number): number => { + const parsed = Number(value); + if (!Number.isFinite(parsed)) { + return fallback; + } + return Math.max(1, Math.floor(parsed)); +}; type RedisLike = Pick< RedisClientType, @@ -318,6 +342,14 @@ const getGenericConfig = (limits: GenericLiveLimits): { parse: (value) => InferredDarkEventSchema.parse(value), cursor: (item) => ({ ts: item.source_ts, seq: item.seq }), fetchRecent: fetchRecentInferredDark + }, + news: { + redisKey: "live:news", + cursorField: "news", + limit: limits.news, + parse: (value) => NewsStorySchema.parse(value), + cursor: (item) => ({ ts: item.published_ts, seq: item.seq }), + fetchRecent: fetchRecentNews } }); @@ -371,23 +403,6 @@ const normalizeGenericItems = ( return sortGenericItems(items, config.cursor).slice(0, config.limit); }; -const extractFreshnessTs = (channel: LiveGenericChannel, item: any): number | null => { - switch (channel) { - case "options": - case "nbbo": - case "equities": - case "equity-quotes": - return typeof item.ts === "number" ? item.ts : null; - case "flow": - case "classifier-hits": - case "alerts": - case "inferred-dark": - return typeof item.source_ts === "number" ? item.source_ts : null; - default: - return null; - } -}; - const isWithinLiveFeedLookback = ( channel: LiveGenericChannel, item: unknown, diff --git a/services/ingest-news/package.json b/services/ingest-news/package.json new file mode 100644 index 0000000..050f40b --- /dev/null +++ b/services/ingest-news/package.json @@ -0,0 +1,16 @@ +{ + "name": "@islandflow/ingest-news", + "private": true, + "type": "module", + "scripts": { + "dev": "bun run src/index.ts" + }, + "dependencies": { + "@islandflow/bus": "workspace:*", + "@islandflow/config": "workspace:*", + "@islandflow/observability": "workspace:*", + "@islandflow/types": "workspace:*", + "ws": "^8.18.3", + "zod": "^3.23.8" + } +} diff --git a/services/ingest-news/src/index.ts b/services/ingest-news/src/index.ts new file mode 100644 index 0000000..3f91ee2 --- /dev/null +++ b/services/ingest-news/src/index.ts @@ -0,0 +1,216 @@ +import { readEnv } from "@islandflow/config"; +import { createLogger } from "@islandflow/observability"; +import { + SUBJECT_NEWS, + STREAM_NEWS, + connectJetStreamWithRetry, + ensureKnownStreams, + publishJson +} from "@islandflow/bus"; +import { NewsStorySchema, type NewsStory } from "@islandflow/types"; +import WebSocket from "ws"; +import { z } from "zod"; +import { resolveNewsSymbols } from "./symbols"; + +const service = "ingest-news"; +const logger = createLogger({ service }); + +const envSchema = z.object({ + NATS_URL: z.string().default("nats://127.0.0.1:4222"), + ALPACA_API_KEY: z.string().default(""), + ALPACA_REST_URL: z.string().default("https://data.alpaca.markets"), + ALPACA_WS_BASE_URL: z.string().default("wss://stream.data.alpaca.markets"), + ALPACA_NEWS_BACKFILL_LIMIT: z.coerce.number().int().positive().max(200).default(100), + ALPACA_NEWS_WEBSOCKET_PATH: z.string().default("/v1beta1/news") +}); + +const env = readEnv(envSchema); + +type AlpacaNewsItem = { + id?: number; + headline?: string; + summary?: string; + content?: string; + author?: string; + created_at?: string; + updated_at?: string; + url?: string; + symbols?: string[]; + source?: string; +}; + +type AlpacaNewsResponse = { + news?: AlpacaNewsItem[]; +}; + +const buildHeaders = (): Record => ({ + Authorization: `Bearer ${env.ALPACA_API_KEY}` +}); + +const parseTimestamp = (value: string | undefined): number => { + const parsed = value ? Date.parse(value) : Number.NaN; + return Number.isFinite(parsed) ? parsed : Date.now(); +}; + +const toStory = (item: AlpacaNewsItem, seq: number): NewsStory | null => { + const storyId = Number(item.id); + if (!Number.isFinite(storyId) || storyId < 0) { + return null; + } + + const provider = "alpaca"; + const contentHtml = item.content ?? ""; + const symbols = resolveNewsSymbols(item.symbols ?? [], contentHtml); + const publishedTs = parseTimestamp(item.created_at); + const updatedTs = parseTimestamp(item.updated_at ?? item.created_at); + + return NewsStorySchema.parse({ + source_ts: publishedTs, + ingest_ts: Date.now(), + seq, + trace_id: `${provider}:${storyId}`, + story_id: storyId, + provider, + source: item.source?.trim() || item.author?.trim() || "Alpaca News", + headline: item.headline?.trim() || `Story ${storyId}`, + summary: item.summary?.trim() || "", + content_html: contentHtml, + url: item.url?.trim() || "", + published_ts: publishedTs, + updated_ts: updatedTs, + provider_symbols: symbols.provider_symbols, + resolved_symbols: symbols.resolved_symbols, + symbol_resolution: symbols.symbol_resolution + }); +}; + +const fetchBackfill = async (): Promise => { + const url = new URL("/v1beta1/news", env.ALPACA_REST_URL); + url.searchParams.set("sort", "desc"); + url.searchParams.set("limit", env.ALPACA_NEWS_BACKFILL_LIMIT.toString()); + + const response = await fetch(url.toString(), { + headers: buildHeaders() + }); + + if (!response.ok) { + throw new Error(`alpaca news backfill failed (${response.status})`); + } + + const payload = (await response.json()) as AlpacaNewsResponse; + return Array.isArray(payload.news) ? payload.news : []; +}; + +const decodePayload = (data: WebSocket.RawData): unknown => { + if (typeof data === "string") { + return JSON.parse(data) as unknown; + } + if (data instanceof ArrayBuffer) { + return JSON.parse(new TextDecoder().decode(new Uint8Array(data))) as unknown; + } + if (ArrayBuffer.isView(data)) { + return JSON.parse(new TextDecoder().decode(new Uint8Array(data.buffer, data.byteOffset, data.byteLength))) as unknown; + } + return JSON.parse(new TextDecoder().decode(new Uint8Array(data as ArrayBuffer))) as unknown; +}; + +const run = async () => { + if (!env.ALPACA_API_KEY) { + throw new Error("ALPACA_API_KEY is required for ingest-news."); + } + + const { nc, js, jsm } = await connectJetStreamWithRetry( + { + servers: env.NATS_URL, + name: service + }, + { attempts: 120, delayMs: 500 } + ); + + await ensureKnownStreams(jsm, [STREAM_NEWS], { logger }); + + let seq = 0; + const publishStory = async (item: AlpacaNewsItem) => { + seq += 1; + const story = toStory(item, seq); + if (!story) { + return; + } + await publishJson(js, SUBJECT_NEWS, story); + }; + + const backfill = await fetchBackfill(); + for (const item of backfill.reverse()) { + await publishStory(item); + } + + const wsUrl = new URL(env.ALPACA_NEWS_WEBSOCKET_PATH, env.ALPACA_WS_BASE_URL).toString(); + const ws = new WebSocket(wsUrl, { + headers: buildHeaders() + }); + + ws.on("open", () => { + ws.send( + JSON.stringify({ + action: "auth", + key: env.ALPACA_API_KEY, + secret: "" + }) + ); + }); + + ws.on("message", (raw) => { + let payload: unknown; + try { + payload = decodePayload(raw); + } catch (error) { + logger.warn("failed to decode alpaca news message", { + error: error instanceof Error ? error.message : String(error) + }); + return; + } + + if (!Array.isArray(payload)) { + return; + } + + for (const entry of payload) { + if (!entry || typeof entry !== "object") { + continue; + } + const message = entry as Record; + if (message.T === "success") { + const msg = typeof message.msg === "string" ? message.msg : ""; + if (msg === "authenticated") { + ws.send(JSON.stringify({ action: "subscribe", news: ["*"] })); + } + continue; + } + if (message.T === "subscription" || message.T === "error") { + continue; + } + void publishStory(message as AlpacaNewsItem).catch((error) => { + logger.error("failed to publish alpaca news story", { + error: error instanceof Error ? error.message : String(error) + }); + }); + } + }); + + const shutdown = async (signal: string) => { + logger.info("shutting down", { signal }); + ws.close(); + await nc.drain(); + process.exit(0); + }; + + process.on("SIGINT", () => void shutdown("SIGINT")); + process.on("SIGTERM", () => void shutdown("SIGTERM")); +}; + +void run().catch((error) => { + logger.error("service crashed", { + error: error instanceof Error ? error.message : String(error) + }); + process.exit(1); +}); diff --git a/services/ingest-news/src/symbols.ts b/services/ingest-news/src/symbols.ts new file mode 100644 index 0000000..e1537fd --- /dev/null +++ b/services/ingest-news/src/symbols.ts @@ -0,0 +1,70 @@ +import type { NewsSymbolResolution } from "@islandflow/types"; + +const TICKER_ANCHOR_RE = />\s*([A-Z]{1,5})\s*<\/a>/g; +const EXCHANGE_TICKER_RE = /\b(?:NASDAQ|NYSE|NYSEAMERICAN|AMEX|OTC|CBOE):([A-Z]{1,5})\b/g; +const DOLLAR_TICKER_RE = /\$([A-Z]{1,5})\b/g; + +const normalizeSymbols = (symbols: string[]): string[] => { + const seen = new Set(); + const normalized: string[] = []; + + for (const entry of symbols) { + const symbol = entry.trim().toUpperCase(); + if (!symbol || !/^[A-Z]{1,5}$/.test(symbol) || seen.has(symbol)) { + continue; + } + seen.add(symbol); + normalized.push(symbol); + } + + return normalized; +}; + +const collectMatches = (value: string, regex: RegExp): string[] => { + regex.lastIndex = 0; + const matches: string[] = []; + let match: RegExpExecArray | null = null; + while ((match = regex.exec(value)) !== null) { + matches.push(match[1] ?? ""); + } + return matches; +}; + +export const resolveNewsSymbols = ( + providerSymbols: string[], + contentHtml: string +): { + provider_symbols: string[]; + resolved_symbols: string[]; + symbol_resolution: NewsSymbolResolution; +} => { + const normalizedProvider = normalizeSymbols(providerSymbols); + const derived = normalizeSymbols([ + ...collectMatches(contentHtml, TICKER_ANCHOR_RE), + ...collectMatches(contentHtml, EXCHANGE_TICKER_RE), + ...collectMatches(contentHtml, DOLLAR_TICKER_RE) + ]); + + if (normalizedProvider.length > 0) { + const merged = normalizeSymbols([...normalizedProvider, ...derived]); + return { + provider_symbols: normalizedProvider, + resolved_symbols: merged, + symbol_resolution: derived.length > 0 ? "mixed" : "provider" + }; + } + + if (derived.length > 0) { + return { + provider_symbols: [], + resolved_symbols: derived, + symbol_resolution: "derived" + }; + } + + return { + provider_symbols: [], + resolved_symbols: [], + symbol_resolution: "none" + }; +}; diff --git a/services/ingest-news/tests/symbols.test.ts b/services/ingest-news/tests/symbols.test.ts new file mode 100644 index 0000000..4f3994e --- /dev/null +++ b/services/ingest-news/tests/symbols.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "bun:test"; +import { resolveNewsSymbols } from "../src/symbols"; + +describe("resolveNewsSymbols", () => { + it("prefers provider symbols when present", () => { + const result = resolveNewsSymbols(["tsla", "aapl"], "

No extra tickers here.

"); + expect(result.provider_symbols).toEqual(["TSLA", "AAPL"]); + expect(result.resolved_symbols).toEqual(["TSLA", "AAPL"]); + expect(result.symbol_resolution).toBe("provider"); + }); + + it("falls back to ticker anchors", () => { + const result = resolveNewsSymbols([], 'TSLA'); + expect(result.resolved_symbols).toEqual(["TSLA"]); + expect(result.symbol_resolution).toBe("derived"); + }); + + it("falls back to exchange and dollar patterns", () => { + const result = resolveNewsSymbols([], "

NASDAQ:TSLA met with $IBM executives.

"); + expect(result.resolved_symbols).toEqual(["TSLA", "IBM"]); + expect(result.symbol_resolution).toBe("derived"); + }); + + it("dedupes and uppercases merged symbols", () => { + const result = resolveNewsSymbols(["tsla"], "

$TSLA and NASDAQ:TSLA

"); + expect(result.provider_symbols).toEqual(["TSLA"]); + expect(result.resolved_symbols).toEqual(["TSLA"]); + expect(result.symbol_resolution).toBe("mixed"); + }); +}); diff --git a/services/ingest-news/tsconfig.json b/services/ingest-news/tsconfig.json new file mode 100644 index 0000000..43ef119 --- /dev/null +++ b/services/ingest-news/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": [] + }, + "include": ["src/**/*.ts", "tests/**/*.ts"] +} From 04baecebe0574ff6ae1a1ed8552271d08c330bba Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Mon, 18 May 2026 21:32:44 -0400 Subject: [PATCH 03/12] update turn docs and beads workflow --- AGENTS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3ab1cf0..8f1971b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -117,8 +117,9 @@ Each turn document must include these sections: 2. **Changes Made** 3. **Context** 4. **Important Implementation Details** -5. **Expected Impact for End-Users** -5. **Validation** +5. **Relevant Diff Snippets** +6. **Expected Impact for End-Users** +7. **Validation** 6. **Issues, Limitations, and Mitigations** 7. **Follow-up Work** From 8173b05c1c71318d5ab10696bfe1fd2d790e7427 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Tue, 19 May 2026 07:05:55 -0400 Subject: [PATCH 04/12] upgrade next.js to 16.2.6 and react 19 --- .beads/issues.jsonl | 1 + AGENTS server.md | 174 ++++++++++++++++++++++++++++++++++++++++++++ AGENTS.md | 5 +- 3 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 AGENTS server.md diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 9909cdd..e7a99aa 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -13,6 +13,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-lib","title":"Upgrade apps/web to Next.js 16.2.6","description":"Upgrade the web app dependency stack to Next.js 16.2.6 with React 19, refresh Bun and mirrored Docker workspace lockfiles, keep runtime behavior unchanged, fix any focused web test fallout, validate the web build and targeted route tests, and document the completed work.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:04:51Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:04:51Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-8fn","title":"implement alpaca-backed news wire view","description":"Why this issue exists and what needs to be done:\\nAdd an Alpaca-powered live news pipeline, API, storage, and web experience, including a dedicated /news route, Home preview, live fanout, history pagination, ticker resolution, and replay-mode live-only empty states.\\n\\nAcceptance criteria:\\n- normalized NewsStory contract and live channel exist\\n- ingest-news service backfills and streams Alpaca news\\n- API persists, serves, and fans out news\\n- web app exposes /news plus Home preview and drawer\\n- tests cover types, storage, API, and key UI behaviors\\n- turn documentation is added\\n\\nDesign:\\nReuse Islandflow drawer, chips, panes, and terminal styling; keep news live-only in v1 replay mode.\\n\\nNotes:\\nImplement client-side ticker filtering in v1 and expose latest revision only per provider+story_id.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T20:37:13Z","created_by":"dirtydishes","updated_at":"2026-05-18T20:55:11Z","started_at":"2026-05-18T20:37:20Z","closed_at":"2026-05-18T20:55:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-k8i","title":"Fix duplicate alert context import in API entrypoint","description":"Recent alert-context work introduced a duplicate fetchAlertContextByTraceId import in services/api/src/index.ts, which risks breaking TypeScript compilation and API startup. Remove the duplicate import and validate the affected API/web tests.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T13:01:58Z","created_by":"dirtydishes","updated_at":"2026-05-18T13:03:40Z","started_at":"2026-05-18T13:02:02Z","closed_at":"2026-05-18T13:03:40Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-lk9","title":"Fix PR creation workflow after Forgejo migration","description":"## Why\\nCreating pull requests with fails after the repository moved primary collaboration from GitHub to Forgejo. The current workflow still assumes GitHub GraphQL PR creation semantics, which do not work against the Forgejo remote.\\n\\n## What\\nInvestigate the current PR creation path, identify remaining GitHub-specific assumptions, and update the repo workflow/scripts/docs so contributors can reliably publish branches and open PRs in the Forgejo-based setup.\\n\\n## Acceptance Criteria\\n- The repo no longer instructs contributors to use a broken GitHub-specific PR creation path for Forgejo branches\\n- There is a documented and preferably scripted way to create the equivalent review request against Forgejo\\n- Validation demonstrates the new workflow behaves correctly or clearly documents any remaining platform limitation","status":"in_progress","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T10:26:47Z","created_by":"dirtydishes","updated_at":"2026-05-18T10:26:53Z","started_at":"2026-05-18T10:26:53Z","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/AGENTS server.md b/AGENTS server.md new file mode 100644 index 0000000..08a484a --- /dev/null +++ b/AGENTS server.md @@ -0,0 +1,174 @@ + +## 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/ +``` +## Important: If you are not working inside a git repository, save the document to `~/dev/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. **Relevant Diff Snippets** +6. **Expected Impact for End-Users** +7. **Validation** +8. **Issues, Limitations, and Mitigations** +9. **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 diff --git a/AGENTS.md b/AGENTS.md index 8f1971b..08a484a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,6 +82,7 @@ Save the document in: ```text docs/turns/ ``` +## Important: If you are not working inside a git repository, save the document to `~/dev/docs/turns/` Use a clear timestamped filename: @@ -120,8 +121,8 @@ Each turn document must include these sections: 5. **Relevant Diff Snippets** 6. **Expected Impact for End-Users** 7. **Validation** -6. **Issues, Limitations, and Mitigations** -7. **Follow-up Work** +8. **Issues, Limitations, and Mitigations** +9. **Follow-up Work** ### Completion Rule From 728ca5569dc27b5f476ca91bddc47a1773c60431 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Tue, 19 May 2026 07:12:06 -0400 Subject: [PATCH 05/12] update beads --- .beads/issues.jsonl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index e7a99aa..493492f 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -13,7 +13,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-lib","title":"Upgrade apps/web to Next.js 16.2.6","description":"Upgrade the web app dependency stack to Next.js 16.2.6 with React 19, refresh Bun and mirrored Docker workspace lockfiles, keep runtime behavior unchanged, fix any focused web test fallout, validate the web build and targeted route tests, and document the completed work.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:04:51Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:04:51Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-lib","title":"Upgrade apps/web to Next.js 16.2.6","description":"Upgrade the web app dependency stack to Next.js 16.2.6 with React 19, refresh Bun and mirrored Docker workspace lockfiles, keep runtime behavior unchanged, fix any focused web test fallout, validate the web build and targeted route tests, and document the completed work.","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:04:51Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:04:57Z","started_at":"2026-05-19T11:04:57Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-8fn","title":"implement alpaca-backed news wire view","description":"Why this issue exists and what needs to be done:\\nAdd an Alpaca-powered live news pipeline, API, storage, and web experience, including a dedicated /news route, Home preview, live fanout, history pagination, ticker resolution, and replay-mode live-only empty states.\\n\\nAcceptance criteria:\\n- normalized NewsStory contract and live channel exist\\n- ingest-news service backfills and streams Alpaca news\\n- API persists, serves, and fans out news\\n- web app exposes /news plus Home preview and drawer\\n- tests cover types, storage, API, and key UI behaviors\\n- turn documentation is added\\n\\nDesign:\\nReuse Islandflow drawer, chips, panes, and terminal styling; keep news live-only in v1 replay mode.\\n\\nNotes:\\nImplement client-side ticker filtering in v1 and expose latest revision only per provider+story_id.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T20:37:13Z","created_by":"dirtydishes","updated_at":"2026-05-18T20:55:11Z","started_at":"2026-05-18T20:37:20Z","closed_at":"2026-05-18T20:55:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-k8i","title":"Fix duplicate alert context import in API entrypoint","description":"Recent alert-context work introduced a duplicate fetchAlertContextByTraceId import in services/api/src/index.ts, which risks breaking TypeScript compilation and API startup. Remove the duplicate import and validate the affected API/web tests.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T13:01:58Z","created_by":"dirtydishes","updated_at":"2026-05-18T13:03:40Z","started_at":"2026-05-18T13:02:02Z","closed_at":"2026-05-18T13:03:40Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-lk9","title":"Fix PR creation workflow after Forgejo migration","description":"## Why\\nCreating pull requests with fails after the repository moved primary collaboration from GitHub to Forgejo. The current workflow still assumes GitHub GraphQL PR creation semantics, which do not work against the Forgejo remote.\\n\\n## What\\nInvestigate the current PR creation path, identify remaining GitHub-specific assumptions, and update the repo workflow/scripts/docs so contributors can reliably publish branches and open PRs in the Forgejo-based setup.\\n\\n## Acceptance Criteria\\n- The repo no longer instructs contributors to use a broken GitHub-specific PR creation path for Forgejo branches\\n- There is a documented and preferably scripted way to create the equivalent review request against Forgejo\\n- Validation demonstrates the new workflow behaves correctly or clearly documents any remaining platform limitation","status":"in_progress","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T10:26:47Z","created_by":"dirtydishes","updated_at":"2026-05-18T10:26:53Z","started_at":"2026-05-18T10:26:53Z","dependency_count":0,"dependent_count":0,"comment_count":0} From b6fa2f0d179ad18c402b40c93a9ba0c1a946ead1 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Tue, 19 May 2026 07:31:41 -0400 Subject: [PATCH 06/12] upgrade web to nextjs 16 --- .beads/issues.jsonl | 2 +- apps/web/app/terminal.tsx | 6 +- apps/web/next-env.d.ts | 3 +- apps/web/package.json | 9 +- bun.lock | 113 ++++++--- deployment/docker/workspace-root/bun.lock | 113 ++++++--- docs/turns/2026-05-19-upgrade-nextjs-16.html | 229 +++++++++++++++++++ 7 files changed, 394 insertions(+), 81 deletions(-) create mode 100644 docs/turns/2026-05-19-upgrade-nextjs-16.html diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 493492f..550d304 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -13,7 +13,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-lib","title":"Upgrade apps/web to Next.js 16.2.6","description":"Upgrade the web app dependency stack to Next.js 16.2.6 with React 19, refresh Bun and mirrored Docker workspace lockfiles, keep runtime behavior unchanged, fix any focused web test fallout, validate the web build and targeted route tests, and document the completed work.","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:04:51Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:04:57Z","started_at":"2026-05-19T11:04:57Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-lib","title":"Upgrade apps/web to Next.js 16.2.6","description":"Upgrade the web app dependency stack to Next.js 16.2.6 with React 19, refresh Bun and mirrored Docker workspace lockfiles, keep runtime behavior unchanged, fix any focused web test fallout, validate the web build and targeted route tests, and document the completed work.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:04:51Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:31:23Z","started_at":"2026-05-19T11:04:57Z","closed_at":"2026-05-19T11:31:23Z","close_reason":"Upgraded apps/web to Next.js 16.2.6 with React 19, refreshed Bun lockfiles including the Docker workspace mirror, fixed the React 19 nullable ref type issue, and validated the web build, focused tests, Docker workspace sync, and route smoke checks.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-8fn","title":"implement alpaca-backed news wire view","description":"Why this issue exists and what needs to be done:\\nAdd an Alpaca-powered live news pipeline, API, storage, and web experience, including a dedicated /news route, Home preview, live fanout, history pagination, ticker resolution, and replay-mode live-only empty states.\\n\\nAcceptance criteria:\\n- normalized NewsStory contract and live channel exist\\n- ingest-news service backfills and streams Alpaca news\\n- API persists, serves, and fans out news\\n- web app exposes /news plus Home preview and drawer\\n- tests cover types, storage, API, and key UI behaviors\\n- turn documentation is added\\n\\nDesign:\\nReuse Islandflow drawer, chips, panes, and terminal styling; keep news live-only in v1 replay mode.\\n\\nNotes:\\nImplement client-side ticker filtering in v1 and expose latest revision only per provider+story_id.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T20:37:13Z","created_by":"dirtydishes","updated_at":"2026-05-18T20:55:11Z","started_at":"2026-05-18T20:37:20Z","closed_at":"2026-05-18T20:55:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-k8i","title":"Fix duplicate alert context import in API entrypoint","description":"Recent alert-context work introduced a duplicate fetchAlertContextByTraceId import in services/api/src/index.ts, which risks breaking TypeScript compilation and API startup. Remove the duplicate import and validate the affected API/web tests.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T13:01:58Z","created_by":"dirtydishes","updated_at":"2026-05-18T13:03:40Z","started_at":"2026-05-18T13:02:02Z","closed_at":"2026-05-18T13:03:40Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-lk9","title":"Fix PR creation workflow after Forgejo migration","description":"## Why\\nCreating pull requests with fails after the repository moved primary collaboration from GitHub to Forgejo. The current workflow still assumes GitHub GraphQL PR creation semantics, which do not work against the Forgejo remote.\\n\\n## What\\nInvestigate the current PR creation path, identify remaining GitHub-specific assumptions, and update the repo workflow/scripts/docs so contributors can reliably publish branches and open PRs in the Forgejo-based setup.\\n\\n## Acceptance Criteria\\n- The repo no longer instructs contributors to use a broken GitHub-specific PR creation path for Forgejo branches\\n- There is a documented and preferably scripted way to create the equivalent review request against Forgejo\\n- Validation demonstrates the new workflow behaves correctly or clearly documents any remaining platform limitation","status":"in_progress","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T10:26:47Z","created_by":"dirtydishes","updated_at":"2026-05-18T10:26:53Z","started_at":"2026-05-18T10:26:53Z","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx index 218e149..3bec184 100644 --- a/apps/web/app/terminal.tsx +++ b/apps/web/app/terminal.tsx @@ -1749,7 +1749,7 @@ export const getOptionTableSnapshot = ( }; type ListScrollState = { - listRef: React.RefObject; + listRef: React.RefObject; listNode: HTMLDivElement | null; setListRef: (node: HTMLDivElement | null) => void; isAtTop: boolean; @@ -1854,7 +1854,7 @@ const useListScroll = (): ListScrollState => { }; const useScrollAnchor = ( - listRef: React.RefObject, + listRef: React.RefObject, isAtTopRef: React.MutableRefObject ) => { const pendingRef = useRef<{ @@ -1996,7 +1996,7 @@ type TapeVirtualRow = { const useTapeVirtualList = ( items: T[], - listRef: React.RefObject, + listRef: React.RefObject, config: TapeVirtualListConfig ): TapeVirtualListResult => { const virtualizer = useVirtualizer({ diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index 40c3d68..9edff1c 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/package.json b/apps/web/package.json index 8ab6906..c6a605e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,13 +11,14 @@ "@islandflow/types": "workspace:*", "@tanstack/react-virtual": "^3.13.24", "lightweight-charts": "^4.2.0", - "next": "^14.2.4", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "next": "^16.2.6", + "react": "^19.2.0", + "react-dom": "^19.2.0" }, "devDependencies": { "@types/node": "^20.14.10", - "@types/react": "^18.3.3", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", "typescript": "^5.5.4" } } diff --git a/bun.lock b/bun.lock index 35e00d7..80788c9 100644 --- a/bun.lock +++ b/bun.lock @@ -26,13 +26,14 @@ "@islandflow/types": "workspace:*", "@tanstack/react-virtual": "^3.13.24", "lightweight-charts": "^4.2.0", - "next": "^14.2.4", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "next": "^16.2.6", + "react": "^19.2.0", + "react-dom": "^19.2.0", }, "devDependencies": { "@types/node": "^20.14.10", - "@types/react": "^18.3.3", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", "typescript": "^5.5.4", }, }, @@ -215,8 +216,60 @@ "@electron/windows-sign": ["@electron/windows-sign@1.2.2", "", { "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", "fs-extra": "^11.1.1", "minimist": "^1.2.8", "postject": "^1.0.0-alpha.6" }, "bin": { "electron-windows-sign": "bin/electron-windows-sign.js" } }, "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ=="], + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + "@inquirer/checkbox": ["@inquirer/checkbox@3.0.1", "", { "dependencies": { "@inquirer/core": "^9.2.1", "@inquirer/figures": "^1.0.6", "@inquirer/type": "^2.0.0", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" } }, "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ=="], "@inquirer/confirm": ["@inquirer/confirm@4.0.1", "", { "dependencies": { "@inquirer/core": "^9.2.1", "@inquirer/type": "^2.0.0" } }, "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w=="], @@ -293,25 +346,23 @@ "@msgpack/msgpack": ["@msgpack/msgpack@3.1.3", "", {}, "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA=="], - "@next/env": ["@next/env@14.2.35", "", {}, "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ=="], + "@next/env": ["@next/env@16.2.6", "", {}, "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@14.2.33", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@14.2.33", "", { "os": "darwin", "cpu": "x64" }, "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@14.2.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@14.2.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@14.2.33", "", { "os": "linux", "cpu": "x64" }, "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@14.2.33", "", { "os": "linux", "cpu": "x64" }, "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@14.2.33", "", { "os": "win32", "cpu": "arm64" }, "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg=="], - "@next/swc-win32-ia32-msvc": ["@next/swc-win32-ia32-msvc@14.2.33", "", { "os": "win32", "cpu": "ia32" }, "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q=="], - - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@14.2.33", "", { "os": "win32", "cpu": "x64" }, "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -335,9 +386,7 @@ "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], - "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], - - "@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="], + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], @@ -365,9 +414,9 @@ "@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="], - "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - "@types/react": ["@types/react@18.3.27", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w=="], + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], @@ -465,15 +514,13 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], - "cacache": ["cacache@16.1.3", "", { "dependencies": { "@npmcli/fs": "^2.1.0", "@npmcli/move-file": "^2.0.0", "chownr": "^2.0.0", "fs-minipass": "^2.1.0", "glob": "^8.0.1", "infer-owner": "^1.0.4", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^9.0.0", "tar": "^6.1.11", "unique-filename": "^2.0.0" } }, "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ=="], "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], - "caniuse-lite": ["caniuse-lite@1.0.30001761", "", {}, "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g=="], + "caniuse-lite": ["caniuse-lite@1.0.30001792", "", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -713,8 +760,6 @@ "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], @@ -745,8 +790,6 @@ "log-update": ["log-update@5.0.1", "", { "dependencies": { "ansi-escapes": "^5.0.0", "cli-cursor": "^4.0.0", "slice-ansi": "^5.0.0", "strip-ansi": "^7.0.1", "wrap-ansi": "^8.0.1" } }, "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw=="], - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], @@ -803,7 +846,7 @@ "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], - "next": ["next@14.2.35", "", { "dependencies": { "@next/env": "14.2.35", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", "styled-jsx": "5.1.1" }, "optionalDependencies": { "@next/swc-darwin-arm64": "14.2.33", "@next/swc-darwin-x64": "14.2.33", "@next/swc-linux-arm64-gnu": "14.2.33", "@next/swc-linux-arm64-musl": "14.2.33", "@next/swc-linux-x64-gnu": "14.2.33", "@next/swc-linux-x64-musl": "14.2.33", "@next/swc-win32-arm64-msvc": "14.2.33", "@next/swc-win32-ia32-msvc": "14.2.33", "@next/swc-win32-x64-msvc": "14.2.33" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig=="], + "next": ["next@16.2.6", "", { "dependencies": { "@next/env": "16.2.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.6", "@next/swc-darwin-x64": "16.2.6", "@next/swc-linux-arm64-gnu": "16.2.6", "@next/swc-linux-arm64-musl": "16.2.6", "@next/swc-linux-x64-gnu": "16.2.6", "@next/swc-linux-x64-musl": "16.2.6", "@next/swc-win32-arm64-msvc": "16.2.6", "@next/swc-win32-x64-msvc": "16.2.6", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw=="], "nice-try": ["nice-try@1.0.5", "", {}, "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="], @@ -897,9 +940,9 @@ "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], - "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "react": ["react@19.2.6", "", {}, "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q=="], - "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + "react-dom": ["react-dom@19.2.6", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.6" } }, "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g=="], "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="], @@ -943,7 +986,7 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], @@ -953,6 +996,8 @@ "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], @@ -985,8 +1030,6 @@ "ssri": ["ssri@9.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q=="], - "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], - "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], @@ -999,7 +1042,7 @@ "strip-outer": ["strip-outer@1.0.1", "", { "dependencies": { "escape-string-regexp": "^1.0.2" } }, "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg=="], - "styled-jsx": ["styled-jsx@5.1.1", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" } }, "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw=="], + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], @@ -1141,8 +1184,6 @@ "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], - "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001792", "", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="], - "cacache/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="], "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], diff --git a/deployment/docker/workspace-root/bun.lock b/deployment/docker/workspace-root/bun.lock index 35e00d7..80788c9 100644 --- a/deployment/docker/workspace-root/bun.lock +++ b/deployment/docker/workspace-root/bun.lock @@ -26,13 +26,14 @@ "@islandflow/types": "workspace:*", "@tanstack/react-virtual": "^3.13.24", "lightweight-charts": "^4.2.0", - "next": "^14.2.4", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "next": "^16.2.6", + "react": "^19.2.0", + "react-dom": "^19.2.0", }, "devDependencies": { "@types/node": "^20.14.10", - "@types/react": "^18.3.3", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", "typescript": "^5.5.4", }, }, @@ -215,8 +216,60 @@ "@electron/windows-sign": ["@electron/windows-sign@1.2.2", "", { "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", "fs-extra": "^11.1.1", "minimist": "^1.2.8", "postject": "^1.0.0-alpha.6" }, "bin": { "electron-windows-sign": "bin/electron-windows-sign.js" } }, "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ=="], + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + "@inquirer/checkbox": ["@inquirer/checkbox@3.0.1", "", { "dependencies": { "@inquirer/core": "^9.2.1", "@inquirer/figures": "^1.0.6", "@inquirer/type": "^2.0.0", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" } }, "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ=="], "@inquirer/confirm": ["@inquirer/confirm@4.0.1", "", { "dependencies": { "@inquirer/core": "^9.2.1", "@inquirer/type": "^2.0.0" } }, "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w=="], @@ -293,25 +346,23 @@ "@msgpack/msgpack": ["@msgpack/msgpack@3.1.3", "", {}, "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA=="], - "@next/env": ["@next/env@14.2.35", "", {}, "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ=="], + "@next/env": ["@next/env@16.2.6", "", {}, "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@14.2.33", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@14.2.33", "", { "os": "darwin", "cpu": "x64" }, "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@14.2.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@14.2.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@14.2.33", "", { "os": "linux", "cpu": "x64" }, "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@14.2.33", "", { "os": "linux", "cpu": "x64" }, "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@14.2.33", "", { "os": "win32", "cpu": "arm64" }, "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg=="], - "@next/swc-win32-ia32-msvc": ["@next/swc-win32-ia32-msvc@14.2.33", "", { "os": "win32", "cpu": "ia32" }, "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q=="], - - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@14.2.33", "", { "os": "win32", "cpu": "x64" }, "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -335,9 +386,7 @@ "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], - "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], - - "@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="], + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], @@ -365,9 +414,9 @@ "@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="], - "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - "@types/react": ["@types/react@18.3.27", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w=="], + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], @@ -465,15 +514,13 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], - "cacache": ["cacache@16.1.3", "", { "dependencies": { "@npmcli/fs": "^2.1.0", "@npmcli/move-file": "^2.0.0", "chownr": "^2.0.0", "fs-minipass": "^2.1.0", "glob": "^8.0.1", "infer-owner": "^1.0.4", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^9.0.0", "tar": "^6.1.11", "unique-filename": "^2.0.0" } }, "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ=="], "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], - "caniuse-lite": ["caniuse-lite@1.0.30001761", "", {}, "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g=="], + "caniuse-lite": ["caniuse-lite@1.0.30001792", "", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -713,8 +760,6 @@ "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], @@ -745,8 +790,6 @@ "log-update": ["log-update@5.0.1", "", { "dependencies": { "ansi-escapes": "^5.0.0", "cli-cursor": "^4.0.0", "slice-ansi": "^5.0.0", "strip-ansi": "^7.0.1", "wrap-ansi": "^8.0.1" } }, "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw=="], - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], @@ -803,7 +846,7 @@ "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], - "next": ["next@14.2.35", "", { "dependencies": { "@next/env": "14.2.35", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", "styled-jsx": "5.1.1" }, "optionalDependencies": { "@next/swc-darwin-arm64": "14.2.33", "@next/swc-darwin-x64": "14.2.33", "@next/swc-linux-arm64-gnu": "14.2.33", "@next/swc-linux-arm64-musl": "14.2.33", "@next/swc-linux-x64-gnu": "14.2.33", "@next/swc-linux-x64-musl": "14.2.33", "@next/swc-win32-arm64-msvc": "14.2.33", "@next/swc-win32-ia32-msvc": "14.2.33", "@next/swc-win32-x64-msvc": "14.2.33" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig=="], + "next": ["next@16.2.6", "", { "dependencies": { "@next/env": "16.2.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.6", "@next/swc-darwin-x64": "16.2.6", "@next/swc-linux-arm64-gnu": "16.2.6", "@next/swc-linux-arm64-musl": "16.2.6", "@next/swc-linux-x64-gnu": "16.2.6", "@next/swc-linux-x64-musl": "16.2.6", "@next/swc-win32-arm64-msvc": "16.2.6", "@next/swc-win32-x64-msvc": "16.2.6", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw=="], "nice-try": ["nice-try@1.0.5", "", {}, "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="], @@ -897,9 +940,9 @@ "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], - "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "react": ["react@19.2.6", "", {}, "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q=="], - "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + "react-dom": ["react-dom@19.2.6", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.6" } }, "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g=="], "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="], @@ -943,7 +986,7 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], @@ -953,6 +996,8 @@ "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], @@ -985,8 +1030,6 @@ "ssri": ["ssri@9.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q=="], - "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], - "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], @@ -999,7 +1042,7 @@ "strip-outer": ["strip-outer@1.0.1", "", { "dependencies": { "escape-string-regexp": "^1.0.2" } }, "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg=="], - "styled-jsx": ["styled-jsx@5.1.1", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" } }, "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw=="], + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], @@ -1141,8 +1184,6 @@ "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], - "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001792", "", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="], - "cacache/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="], "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], diff --git a/docs/turns/2026-05-19-upgrade-nextjs-16.html b/docs/turns/2026-05-19-upgrade-nextjs-16.html new file mode 100644 index 0000000..cdbb2f1 --- /dev/null +++ b/docs/turns/2026-05-19-upgrade-nextjs-16.html @@ -0,0 +1,229 @@ + + + + + + Upgrade apps/web to Next.js 16.2.6 + + + + + + +
+
+

Turn document · 2026-05-19

+

Upgrade apps/web to Next.js 16.2.6

+

The web app now builds and passes focused validation on Next.js 16.2.6 with React 19. The change keeps route behavior and synthetic admin proxy behavior intact while refreshing the root and Docker workspace Bun lockfiles.

+
+ +
+

Summary

+

Upgraded apps/web from the Next 14 / React 18 stack to Next 16.2.6 and React 19.2.x. The Bun lockfile was refreshed, the Docker workspace lock snapshot was synced, and a React 19 nullable ref type issue exposed by the Next 16 build was fixed.

+
+ +
+

Changes Made

+
    +
  • Updated apps/web/package.json to request next ^16.2.6, react ^19.2.0, and react-dom ^19.2.0.
  • +
  • Updated React type dependencies to @types/react ^19.2.7 and added @types/react-dom ^19.2.3.
  • +
  • Ran bun install, which resolved Next to 16.2.6 and React/React DOM to 19.2.6 in bun.lock.
  • +
  • Ran bun run sync:docker-workspace so deployment/docker/workspace-root/bun.lock matches the root lock snapshot.
  • +
  • Adjusted the terminal list ref types to accept HTMLDivElement | null, matching React 19's stricter ref object typing.
  • +
  • Allowed Next 16 to regenerate apps/web/next-env.d.ts with its updated TypeScript reference comment and generated route type import.
  • +
+
+ +
+

Context

+

The requested upgrade was intentionally dependency-focused. No routes, backend contracts, environment variable names, or shared package exports were changed. Before editing, the web build and the targeted route tests passed on the previous locked Next 14.2.35 stack.

+
+ +
+

Important Implementation Details

+

No broad codemod was run. The only source-code change was a targeted type correction in apps/web/app/terminal.tsx. Next 16's build now runs with Turbopack by default in this project and completed successfully after the ref typing was narrowed to the actual nullable runtime value.

+

The Docker workspace sync changed the mirrored lockfile, but did not need to rewrite the mirrored package manifest or TypeScript base config.

+
+ +
+

Relevant Diff Snippets

+
"next": "^16.2.6",
+"react": "^19.2.0",
+"react-dom": "^19.2.0",
+"@types/react": "^19.2.7",
+"@types/react-dom": "^19.2.3"
+
type ListScrollState = {
+  listRef: React.RefObject<HTMLDivElement | null>;
+  listNode: HTMLDivElement | null;
+  setListRef: (node: HTMLDivElement | null) => void;
+};
+
+ +
+

Expected Impact for End-Users

+

There should be no intentional user-facing behavior change. The expected visible behavior remains: /, /tape, and /news render the terminal app; /signals, /charts, and /replay redirect to /; synthetic admin API routes keep their gated proxy behavior.

+
+ +
+

Validation

+
    +
  • Baseline before edits: bun --cwd=apps/web run build passed on Next 14.2.35.
  • +
  • Baseline before edits: bun test apps/web/app/routes.test.ts passed, 3 tests.
  • +
  • Baseline before edits: bun test apps/web/app/terminal.test.ts passed, 70 tests.
  • +
  • Baseline before edits: bun test apps/web/app/api/admin/synthetic/routes.test.ts passed, 4 tests.
  • +
  • After upgrade: bun --cwd=apps/web run build passed on Next 16.2.6.
  • +
  • After upgrade: bun test apps/web/app/routes.test.ts passed, 3 tests.
  • +
  • After upgrade: bun test apps/web/app/terminal.test.ts passed, 70 tests.
  • +
  • After upgrade: bun test apps/web/app/api/admin/synthetic/routes.test.ts passed, 4 tests.
  • +
  • After upgrade: bun run check:docker-workspace passed.
  • +
  • Manual smoke: bun run dev:web served Next 16.2.6 on localhost:3000.
  • +
  • Manual smoke: browser checks confirmed /, /tape, and /news render with title Islandflow Terminal.
  • +
  • Manual smoke: /signals, /charts, and /replay returned 307 redirects to /.
  • +
  • Manual smoke: synthetic admin status and control routes returned gated 404 responses when the internal UI flag was off.
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+

During dev:web smoke testing, the browser logged a live socket channel validation warning because only the web app was running, not the full backend service stack. Route rendering, redirect behavior, and gated synthetic admin proxy behavior were still verified. A full-stack live feed verification can be done separately with bun run dev if needed.

+

The upgrade did not include a full monorepo test run because the acceptance bar was intentionally web-focused.

+
+ +
+

Follow-up Work

+
    +
  • No required follow-up Beads issue was opened for this upgrade.
  • +
  • Optional: run a full-stack live feed smoke with infra and services running if you want runtime stream confidence beyond the web-focused acceptance checks.
  • +
  • Optional: run the full monorepo bun test suite before a larger release branch merge.
  • +
+
+ +
+

Helpful Links

+ +
+
+ + From 82fd29f1a451a46b7f90a89201e78966581aebe7 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Tue, 19 May 2026 07:40:18 -0400 Subject: [PATCH 07/12] update readme for current project state --- .beads/issues.jsonl | 1 + README.md | 389 +++++++++--------- ...5-19-0739-update-readme-current-state.html | 259 ++++++++++++ 3 files changed, 449 insertions(+), 200 deletions(-) create mode 100644 docs/turns/2026-05-19-0739-update-readme-current-state.html diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 550d304..61aef8b 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -13,6 +13,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-6iq","title":"Update README for current project state","description":"Resolve README merge conflicts and document the current project state, including the smart money classification taxonomy, Next.js update, and deployment workflow changes.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:37:24Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:40:01Z","started_at":"2026-05-19T11:37:31Z","closed_at":"2026-05-19T11:40:01Z","close_reason":"README conflict resolved and current project state documented, including smart-money taxonomy, Next.js update, and deployment workflow.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-lib","title":"Upgrade apps/web to Next.js 16.2.6","description":"Upgrade the web app dependency stack to Next.js 16.2.6 with React 19, refresh Bun and mirrored Docker workspace lockfiles, keep runtime behavior unchanged, fix any focused web test fallout, validate the web build and targeted route tests, and document the completed work.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:04:51Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:31:23Z","started_at":"2026-05-19T11:04:57Z","closed_at":"2026-05-19T11:31:23Z","close_reason":"Upgraded apps/web to Next.js 16.2.6 with React 19, refreshed Bun lockfiles including the Docker workspace mirror, fixed the React 19 nullable ref type issue, and validated the web build, focused tests, Docker workspace sync, and route smoke checks.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-8fn","title":"implement alpaca-backed news wire view","description":"Why this issue exists and what needs to be done:\\nAdd an Alpaca-powered live news pipeline, API, storage, and web experience, including a dedicated /news route, Home preview, live fanout, history pagination, ticker resolution, and replay-mode live-only empty states.\\n\\nAcceptance criteria:\\n- normalized NewsStory contract and live channel exist\\n- ingest-news service backfills and streams Alpaca news\\n- API persists, serves, and fans out news\\n- web app exposes /news plus Home preview and drawer\\n- tests cover types, storage, API, and key UI behaviors\\n- turn documentation is added\\n\\nDesign:\\nReuse Islandflow drawer, chips, panes, and terminal styling; keep news live-only in v1 replay mode.\\n\\nNotes:\\nImplement client-side ticker filtering in v1 and expose latest revision only per provider+story_id.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T20:37:13Z","created_by":"dirtydishes","updated_at":"2026-05-18T20:55:11Z","started_at":"2026-05-18T20:37:20Z","closed_at":"2026-05-18T20:55:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-k8i","title":"Fix duplicate alert context import in API entrypoint","description":"Recent alert-context work introduced a duplicate fetchAlertContextByTraceId import in services/api/src/index.ts, which risks breaking TypeScript compilation and API startup. Remove the duplicate import and validate the affected API/web tests.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T13:01:58Z","created_by":"dirtydishes","updated_at":"2026-05-18T13:03:40Z","started_at":"2026-05-18T13:02:02Z","closed_at":"2026-05-18T13:03:40Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/README.md b/README.md index 50063d9..d7b8ace 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,12 @@ > **Pre-alpha warning** This project is in an early pre-alpha state. It will not perform consistently or as expected, and APIs, behavior, and data contracts may change without notice. -This repository contains a Bun + TypeScript monorepo for a personal-use, event-sourced market microstructure research platform focused on: +Islandflow is a Bun + TypeScript monorepo for a personal-use, event-sourced market microstructure research platform focused on: - options prints + NBBO, - off-exchange equity prints, -- explainable rule-based flow classification, +- market news context, +- explainable smart-money flow classification, - deterministic replay, - evidence-linked UI inspection. @@ -19,124 +20,175 @@ This repository contains a Bun + TypeScript monorepo for a personal-use, event-s Implemented now: - Bun workspaces with shared packages for schemas, bus, config, observability, and ClickHouse access. -- Infra orchestration via Docker Compose (NATS JetStream, ClickHouse, Redis). -- Options ingest service with adapters: - - synthetic stream, - - Alpaca options (dev-focused, bounded contracts), - - IBKR bridge (Python sidecar), - - Databento historical replay adapter (Python sidecar). -- Equities ingest service with adapters: - - synthetic stream, - - Alpaca equities trades/quotes. -- Compute service: - - deterministic option print clustering into `FlowPacket`s, - - NBBO join quality features and aggressor-mix metrics, - - rolling baselines in Redis, - - structure summarization and structure packet emission, - - rule-based classifiers + confidence-scored alert events, - - dark-style inferred events from equity prints/quotes, - - equity print-to-quote join events. -- Candles service: - - server-side equity candle aggregation, - - ClickHouse persistence, - - optional Redis hot cache, - - NATS publication. -- Replay service: - - deterministic republishing from ClickHouse to NATS, - - multi-stream merge with stable tie-break ordering, - - speed/start/end controls. -- API service: - - REST endpoints for recent + cursor pagination, - - REST range endpoints for chart windows, - - REST replay-oriented endpoints, - - WebSocket channels for options, NBBO, equities, quotes, joins, flow, classifier hits, alerts, inferred dark, and candles. -- Next.js web app: - - live tape/workspace views, - - replay controls and status, - - signals and chart-focused routes, - - evidence-centric terminal UI. -- Refdata + EOD enricher service entrypoints are present but currently scaffolds (lifecycle/logging only). +- Infra orchestration via Docker Compose for local NATS JetStream, ClickHouse, and Redis. +- Options ingest service with synthetic, Alpaca options, IBKR bridge, and Databento historical replay adapters. +- Equities ingest service with synthetic and Alpaca equities trades/quotes adapters. +- News ingest service for Alpaca news backfill and websocket publication. +- Compute service for deterministic parent-event reconstruction, flow packets, NBBO quality features, rolling baselines, smart-money profile scoring, compatibility classifier hits, alerts, inferred dark-style events, and equity print-to-quote joins. +- Candles service for server-side equity candle aggregation, ClickHouse persistence, optional Redis hot cache, and NATS publication. +- Replay service for deterministic ClickHouse-to-NATS republishing with multi-stream merge, stable tie-break ordering, speed, start, and end controls. +- API service with REST endpoints, cursor pagination, replay/history endpoints, live hot-cache hydration, and WebSocket channels for options, NBBO, equities, quotes, joins, flow, classifier hits, alerts, smart-money events, inferred dark, candles, and news. +- Next.js web app upgraded to Next.js `16.2.6`, React `19.2.0`, and React DOM `19.2.0`. +- Evidence-centric terminal UI, live/replay controls, chart-focused routes, news view, profile-aware smart-money display, and alert-context hydration. +- Thin Electron desktop shell in `apps/desktop` that can wrap the hosted app or local web UI. +- Refdata + EOD enricher service entrypoints are present, with refdata able to validate or refresh the event-calendar cache. Planned / not yet complete: - production-grade licensed feed integrations and entitlement workflow, - richer refdata/corp-action enrichment, - secure deployment/auth hardening, -- deeper structure + calibration workflows from `PLAN.md`. +- native deployment unit templates and rollback helpers, +- signed/notarized desktop distribution and richer desktop-native features, +- deeper calibration workflows from `PLAN.md` and `SMART_MONEY_REBUILD_PLAN.md`. ## Core Principles -- **Explainability first** — inferred outputs are evidence-backed and human-readable. -- **Event sourcing** — raw and derived events persist to support replay. -- **Determinism** — replay behavior tracks live pipeline logic. -- **Microstructure awareness** — bounded joins, confidence scoring, and explicit uncertainty. -- **Bun-first tooling** — runtime/package/scripts all use Bun. +- **Explainability first**: inferred outputs are evidence-backed and human-readable. +- **Event sourcing**: raw and derived events persist to support replay. +- **Determinism**: replay behavior tracks live pipeline logic. +- **Microstructure awareness**: bounded joins, confidence scoring, and explicit uncertainty. +- **Taxonomy over folklore**: "smart money" is modeled as participant-style hypotheses, not a single binary label. +- **Bun-first tooling**: runtime, package management, scripts, and tests use Bun. + +## Smart-Money Classification Taxonomy + +Islandflow now emits first-class `SmartMoneyEvent` records instead of treating old classifier hits as the final semantic object. `FlowPacket` remains the clustering bridge, while smart-money events carry typed features, profile scores, confidence bands, directions, reason codes, abstention state, and suppression reasons. + +Public profile IDs: + +| Profile ID | Meaning | Common evidence | +| --- | --- | --- | +| `institutional_directional` | Large directional parent flow with stronger institutional-style conviction. | premium, size, sweep/burst behavior, aggressor imbalance, quote quality, not short-dated retail-chase context | +| `retail_whale` | Large retail-style speculative bursts, often short-dated or attention-driven. | short-dated OTM concentration, burst prints, IV shock, lower premium than institutional blocks | +| `event_driven` | Flow aligned to known upcoming events. | event-calendar proximity, expiry after event, pre-event concentration, spread/IV pressure | +| `vol_seller` | Premium-selling or short-volatility structure evidence. | sell-side premium, straddles/strangles, neutral direction | +| `arbitrage` | Multi-leg or symmetric structures with low directional exposure. | matched leg symmetry, same-size legs, near-flat directional bias | +| `hedge_reactive` | Hedge or dealer-reaction style flow around short-dated ATM/gamma context. | 0-2 DTE, near-ATM contracts, underlying move linkage, size | + +Compatibility surfaces remain in place: + +- `ClassifierHitEvent` is derived from `SmartMoneyEvent.primary_profile_id`. +- `AlertEvent` may include `primary_profile_id` and `profile_scores`. +- Legacy classifier and alert endpoints still work. + +Primary smart-money access paths: + +```text +/flow/smart-money +/history/smart-money +/replay/smart-money +/ws/smart-money +``` + +The classifier intentionally abstains when evidence is weak or quote context is stale/missing. Suppression guards cover stale quotes, complex/special prints, retail-frenzy directional confusion, hedge-reactive short-dated ATM contexts, and arbitrage symmetry. ## Monorepo Layout - `apps/web` — Next.js UI shell/routes. -- `apps/desktop` — Electron desktop shell that loads the hosted Islandflow app. +- `apps/desktop` — Electron desktop shell that loads the hosted or local Islandflow app. - `services/ingest-options` — options print/NBBO ingest adapters. - `services/ingest-equities` — equity print/quote ingest adapters. -- `services/compute` — clustering, structures, classifiers, alerts, inferred dark. +- `services/ingest-news` — Alpaca news backfill and websocket ingest. +- `services/compute` — parent-event reconstruction, flow packets, smart-money scoring, alerts, inferred dark. - `services/candles` — server-side candle aggregation + cache. -- `services/replay` — ClickHouse → NATS replay streamer. +- `services/replay` — ClickHouse to NATS replay streamer. - `services/api` — REST + WebSocket gateway. -- `services/refdata` — scaffold service. +- `services/refdata` — event-calendar validation/provider refresh scaffolding. - `services/eod-enricher` — scaffold service. - `packages/types` — shared event schemas/types. - `packages/storage` — ClickHouse tables/queries. - `packages/bus` — NATS/JetStream helpers. - `packages/config` — env parsing. - `packages/observability` — logger + metrics facade. +- `deployment/docker` — supported VPS Docker Compose runtime. +- `deployment/native` — experimental host-native Bun + systemd deployment notes. ## Build and Run Install dependencies: -- `bun install` +```bash +bun install +``` Start infrastructure only: -- `docker compose up -d` +```bash +bun run dev:infra +``` Create env file: -- copy `.env.example` to `.env` and set provider credentials as needed. +```bash +cp .env.example .env +``` Start infra + all services + web: -- `bun run dev` +```bash +bun run dev +``` -Start services only (assumes infra is already running): +Start services only, assuming infra is already running: -- `bun run dev:services` +```bash +bun run dev:services +``` Start web only: -- `bun run dev:web` +```bash +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 +```bash +bun run dev:infra +bun run dev:services +bun run dev:web +``` -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. +This keeps Docker in the local workflow where it helps most, for NATS, ClickHouse, and Redis, while keeping the app services in native Bun/Next.js loops. ## 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`. -- Docker runtime details live in `deployment/docker/README.md`. -- Native runtime expectations and prerequisites live in `deployment/native/README.md`. +Docker remains the supported and recommended path for the current VPS. + +```bash +./deploy main +./deploy main --runtime docker +./deploy current-branch +./deploy current-branch --runtime docker +``` + +Important deployment notes: + +- Run the deploy helper from the local repo checkout, not from the VPS shell. +- Do not run the repo-root `docker-compose.yml` on the VPS. It is local infra only and can create duplicate exposed NATS, ClickHouse, and Redis containers on the server. +- The Docker stack lives in `deployment/docker` and is separate from local development infra. +- Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, `--fast`, `--no-build`, and `--force-recreate`. +- `--fast` defaults to a services-only Docker rollout when no explicit scope is provided and trims public API route-suite verification while preserving remote service health checks. +- `./deploy current-branch` requires a clean local working tree and pushes the branch before moving the server checkout. +- The helper has Forgejo-aware remote resolution for deployments and branch pushes. +- Native deployment is opt-in and experimental: + +```bash +./deploy main --runtime native +./deploy current-branch --runtime native +``` + +Native deployment expects Bun, systemd units, host-reachable infra, and deliberate reverse-proxy changes. The open follow-up is to add native unit templates and rollback helpers. + +Read more: + +- `deployment/docker/README.md` +- `deployment/native/README.md` ## Desktop Shell -Islandflow also includes a thin Electron desktop shell in `apps/desktop`. +Islandflow includes a thin Electron desktop shell in `apps/desktop`. What it is: @@ -144,37 +196,35 @@ What it is: - a native app window plus packaging/distribution shell, - a way to run the existing web UI inside Electron without local backend services. -What it is not: +What it is not yet: - a bundled backend runtime, -- a packaged local Next.js frontend in v1, -- a desktop feature layer with notifications, preferences, or auto-updates yet. +- a packaged local Next.js frontend, +- a desktop feature layer with notifications, preferences, auto-updates, signing, or notarization. Run the desktop shell against a local web UI: -- `bun run dev:desktop` - -This starts the local Next.js app, defaults `NEXT_PUBLIC_API_URL` to `https://flow.deltaisland.io` unless you already set it, waits for port `3000`, and then launches Electron against `http://127.0.0.1:3000`. +```bash +bun run dev:desktop +``` Run the desktop shell directly against the hosted app: -- `bun run dev:desktop:remote` +```bash +bun run dev:desktop:remote +``` Package the desktop shell: -- `bun run package:desktop` -- `bun run make:desktop` +```bash +bun run package:desktop +bun run make:desktop +``` Desktop-specific environment: - `ISLANDFLOW_DESKTOP_START_URL` is only used by the Electron shell and is restricted to trusted Islandflow app origins. -- `NEXT_PUBLIC_API_URL` remains the web app's API/WebSocket origin control and should usually point at `https://flow.deltaisland.io` when developing the local UI inside Electron. - -Current desktop limitations: - -- v1 builds are unsigned internal macOS artifacts only, -- Forge currently makes a simple zip distributable for the current host architecture, -- signing, notarization, auto-updates, remembered window state, and richer native integrations are intentionally deferred. +- `NEXT_PUBLIC_API_URL` remains the web app API/WebSocket origin control and usually points at `https://flow.deltaisland.io` when developing local UI inside Electron. ## Environment Configuration @@ -196,32 +246,27 @@ All runtime configuration comes from `.env`. | `OPTIONS_INGEST_ADAPTER` | `synthetic` | Options ingest source: `synthetic`, `alpaca`, `ibkr`, or `databento`. | | `EQUITIES_INGEST_ADAPTER` | `synthetic` | Equities ingest source: `synthetic` or `alpaca`. | | `EMIT_INTERVAL_MS` | `1000` | Emit cadence for synthetic ingest adapters. | -| `SYNTHETIC_MARKET_MODE` | `realistic` | Shared synthetic profile (`realistic`, `active`, `firehose`) used when per-service override is unset. | -| `SYNTHETIC_OPTIONS_MODE` | empty | Options-only synthetic profile override; falls back to `SYNTHETIC_MARKET_MODE`. | -| `SYNTHETIC_EQUITIES_MODE` | empty | Equities-only synthetic profile override; falls back to `SYNTHETIC_MARKET_MODE`. | +| `SYNTHETIC_MARKET_MODE` | `realistic` | Shared synthetic profile: `realistic`, `active`, or `firehose`. | +| `SYNTHETIC_OPTIONS_MODE` | empty | Options-only synthetic profile override. | +| `SYNTHETIC_EQUITIES_MODE` | empty | Equities-only synthetic profile override. | -Synthetic profile intent: -- `realistic`: default local mode with lower synthetic burstiness/noise. -- `active`: busier demo flow while still readable. -- `firehose`: stress mode for throughput/backpressure/hot-window behavior. - -### Options ingest adapter configuration +### Alpaca and news configuration | Variable | Default | What it controls | | --- | --- | --- | -| `ALPACA_API_KEY` | empty | Single-token Alpaca API auth for options/equities adapters. Use this when your account provides one API key value. | -| `ALPACA_REST_URL` | `https://data.alpaca.markets` | Alpaca REST base URL for contract discovery/reference calls. | -| `ALPACA_WS_BASE_URL` | `wss://stream.data.alpaca.markets/v1beta1` (options), `wss://stream.data.alpaca.markets` (equities) | Alpaca websocket base URL. | -| `ALPACA_FEED` | `indicative` | Options feed tier for Alpaca options (`indicative` or `opra`). | +| `ALPACA_API_KEY` | empty | Single-token Alpaca API auth for options, equities, and news adapters. | +| `ALPACA_REST_URL` | `https://data.alpaca.markets` | Alpaca REST base URL. | +| `ALPACA_WS_BASE_URL` | `wss://stream.data.alpaca.markets/v1beta1` for options, `wss://stream.data.alpaca.markets` for equities/news | Alpaca websocket base URL. | +| `ALPACA_FEED` | `indicative` | Options feed tier: `indicative` or `opra`. | | `ALPACA_UNDERLYINGS` | `SPY,NVDA,AAPL` | Comma-separated symbols targeted by Alpaca ingest. | | `ALPACA_STRIKES_PER_SIDE` | `8` | Contracts selected per side of spot for Alpaca options chain sampling. | | `ALPACA_MAX_DTE_DAYS` | `30` | Max days-to-expiry included for Alpaca options contract selection. | | `ALPACA_MONEYNESS_PCT` | `0.06` | Primary moneyness filter for Alpaca options contract selection. | | `ALPACA_MONEYNESS_FALLBACK_PCT` | `0.1` | Wider fallback moneyness filter if candidate set is too sparse. | | `ALPACA_MAX_QUOTES` | `200` | Upper bound on selected Alpaca options contracts/quotes per cycle. | -| `ALPACA_EQUITIES_FEED` | `iex` | Alpaca equities feed (`iex` free tier, `sip` paid consolidated feed). | - -For Alpaca adapters, configure `ALPACA_API_KEY`. +| `ALPACA_EQUITIES_FEED` | `iex` | Alpaca equities feed: `iex` or `sip`. | +| `ALPACA_NEWS_BACKFILL_LIMIT` | `100` | Alpaca news stories fetched on startup, capped at 200. | +| `ALPACA_NEWS_WEBSOCKET_PATH` | `/v1beta1/news` | Alpaca news websocket path. | ### Databento replay adapter configuration @@ -236,7 +281,7 @@ For Alpaca adapters, configure `ALPACA_API_KEY`. | `DATABENTO_SYMBOLS` | `ALL` | Symbol selection forwarded to Databento sidecar query. | | `DATABENTO_STYPE_IN` | `raw_symbol` | Databento input symbology type. | | `DATABENTO_STYPE_OUT` | `raw_symbol` | Databento output symbology type. | -| `DATABENTO_LIMIT` | `0` | Max Databento records (`0` means no explicit limit). | +| `DATABENTO_LIMIT` | `0` | Max Databento records, where `0` means no explicit limit. | | `DATABENTO_PRICE_SCALE` | `1` | Multiplier applied to decoded prices from sidecar output. | | `DATABENTO_PYTHON_BIN` | `python3` | Python executable used to run Databento sidecar script. | @@ -248,9 +293,9 @@ For Alpaca adapters, configure `ALPACA_API_KEY`. | `IBKR_PORT` | `7497` | TWS/Gateway port for IBKR bridge. | | `IBKR_CLIENT_ID` | `0` | IBKR client id used by the bridge connection. | | `IBKR_SYMBOL` | `SPY` | Underlying symbol requested from IBKR. | -| `IBKR_EXPIRY` | `20250117` | Option expiry (YYYYMMDD) requested from IBKR. | +| `IBKR_EXPIRY` | `20250117` | Option expiry requested from IBKR. | | `IBKR_STRIKE` | `450` | Strike requested from IBKR. | -| `IBKR_RIGHT` | `C` | Option side (`C` or `P`). | +| `IBKR_RIGHT` | `C` | Option side: `C` or `P`. | | `IBKR_EXCHANGE` | `SMART` | IBKR exchange routing code. | | `IBKR_CURRENCY` | `USD` | Contract currency. | | `IBKR_PYTHON_BIN` | `python3` | Python executable used for IBKR sidecar. | @@ -259,133 +304,77 @@ For Alpaca adapters, configure `ALPACA_API_KEY`. | Variable | Default | What it controls | | --- | --- | --- | -| `OPTIONS_SIGNAL_MODE` | `smart-money` | Signal pass policy (`smart-money`, `balanced`, `all`) for options prints. | +| `OPTIONS_SIGNAL_MODE` | `smart-money` | Signal pass policy: `smart-money`, `balanced`, or `all`. | | `OPTIONS_SIGNAL_MIN_NOTIONAL` | `10000` | Base minimum notional for most signal candidates. | | `OPTIONS_SIGNAL_ETF_MIN_NOTIONAL` | `50000` | ETF-specific minimum notional for signal inclusion. | -| `OPTIONS_SIGNAL_BID_SIDE_MIN_NOTIONAL` | `25000` | Minimum notional for bid-side (`B`/`BB`) or sweep/ISO thresholds. | +| `OPTIONS_SIGNAL_BID_SIDE_MIN_NOTIONAL` | `25000` | Minimum notional for bid-side or sweep/ISO thresholds. | | `OPTIONS_SIGNAL_MID_MIN_NOTIONAL` | `20000` | Minimum notional for non-sweep/non-ISO `MID` prints. | | `OPTIONS_SIGNAL_NBBO_MAX_AGE_MS` | `1500` | NBBO freshness threshold used during signal classification. | -| `OPTIONS_SIGNAL_ETF_UNDERLYINGS` | `SPY,QQQ,IWM,DIA,TLT,GLD,SLV,XLF,XLE,XLV,XLI,XLP,XLU,XLY,SMH,ARKK` | Comma-separated underlyings treated as ETFs by signal filters. | +| `OPTIONS_SIGNAL_ETF_UNDERLYINGS` | `SPY,QQQ,IWM,DIA,TLT,GLD,SLV,XLF,XLE,XLV,XLI,XLP,XLU,XLY,SMH,ARKK` | ETF underlyings treated specially by signal filters. | -Default `smart-money` policy rejects lower-information prints and keeps high-confidence/high-notional/sweep-style flow; `balanced` lowers thresholds; `all` bypasses filtering. +Default `smart-money` policy rejects lower-information prints and keeps higher-confidence, higher-notional, sweep-style flow. `balanced` lowers thresholds. `all` bypasses filtering. -### Compute/classifier/dark-inference configuration +### Compute, classifier, and dark-inference configuration | Variable | Default | What it controls | | --- | --- | --- | -| `CLUSTER_WINDOW_MS` | `500` | Time window used to cluster nearby option prints into a packet candidate. | -| `COMPUTE_DELIVER_POLICY` | `new` | Consumer start policy for compute stream subscriptions (`new`, `all`, `last`, `last_per_subject`). | -| `COMPUTE_CONSUMER_RESET` | `false` | If true, resets durable consumer position for compute on startup. | +| `CLUSTER_WINDOW_MS` | `500` | Time window used to cluster nearby option prints into packet candidates. | +| `COMPUTE_DELIVER_POLICY` | `new` | Consumer start policy for compute subscriptions. | +| `COMPUTE_CONSUMER_RESET` | `false` | Resets durable consumer position for compute on startup when true. | | `NBBO_MAX_AGE_MS` | `1000` | Max NBBO age accepted when enriching option prints in compute. | | `ROLLING_WINDOW_SIZE` | `50` | Number of observations retained per rolling metric key. | | `ROLLING_TTL_SEC` | `86400` | Redis TTL for rolling metric keys. | | `EQUITY_QUOTE_MAX_AGE_MS` | `1000` | Max quote staleness when joining equity prints for inference. | | `DARK_INFER_WINDOW_MS` | `60000` | Sliding window length for dark-style inference accumulation. | -| `DARK_INFER_COOLDOWN_MS` | `30000` | Cooldown before emitting repeated dark inferences for same symbol/pattern. | -| `DARK_INFER_MIN_BLOCK_SIZE` | `2000` | Minimum single-print size for block-style dark inference evidence. | -| `DARK_INFER_MIN_ACCUM_SIZE` | `3000` | Minimum aggregate size for accumulation-style dark inference evidence. | -| `DARK_INFER_MIN_ACCUM_COUNT` | `4` | Minimum print count for accumulation-style dark inference. | -| `DARK_INFER_MIN_PRINT_SIZE` | `200` | Minimum print size considered as dark inference evidence. | -| `DARK_INFER_MAX_EVIDENCE` | `20` | Max evidence items attached to one inferred dark event. | -| `DARK_INFER_MAX_SPREAD_PCT` | `0.005` | Maximum spread percentage allowed for dark inference confidence. | -| `CLASSIFIER_SWEEP_MIN_PREMIUM` | `40000` | Minimum premium to trigger sweep classifier logic. | -| `CLASSIFIER_SWEEP_MIN_COUNT` | `3` | Minimum child prints in cluster for sweep classifier hit. | -| `CLASSIFIER_SWEEP_MIN_PREMIUM_Z` | `2` | Min premium z-score for sweep classifier confirmation. | -| `CLASSIFIER_SPIKE_MIN_PREMIUM` | `20000` | Minimum premium for spike classifier logic. | -| `CLASSIFIER_SPIKE_MIN_SIZE` | `400` | Minimum total size for spike classifier logic. | -| `CLASSIFIER_SPIKE_MIN_PREMIUM_Z` | `2.5` | Min premium z-score for spike classifier confirmation. | -| `CLASSIFIER_SPIKE_MIN_SIZE_Z` | `2` | Min size z-score for spike classifier confirmation. | -| `CLASSIFIER_Z_MIN_SAMPLES` | `12` | Minimum rolling sample count before z-score gating applies. | -| `CLASSIFIER_MIN_NBBO_COVERAGE` | `0.5` | Required fraction of prints in cluster with valid NBBO context. | -| `CLASSIFIER_MIN_AGGRESSOR_RATIO` | `0.55` | Minimum aggressor-side ratio for classifier confidence. | -| `CLASSIFIER_0DTE_MAX_ATM_PCT` | `0.01` | Max distance-from-ATM to qualify as near-ATM 0DTE event. | -| `CLASSIFIER_0DTE_MIN_PREMIUM` | `20000` | Minimum premium for 0DTE classifier events. | -| `CLASSIFIER_0DTE_MIN_SIZE` | `400` | Minimum size for 0DTE classifier events. | -| `SMART_MONEY_EVENT_CALENDAR_PATH` | empty | Optional JSON event-calendar file used by compute to enrich event-driven smart-money profile features. | -| `REFDATA_EVENT_CALENDAR_PATH` | empty | Optional JSON event-calendar file for refdata service startup validation; falls back to `SMART_MONEY_EVENT_CALENDAR_PATH` when unset. | -| `REFDATA_EVENT_CALENDAR_PROVIDER` | empty | Set to `alpha_vantage` to have refdata refresh the calendar cache from Alpha Vantage. | -| `ALPHA_VANTAGE_API_KEY` | empty | Alpha Vantage key used when `REFDATA_EVENT_CALENDAR_PROVIDER=alpha_vantage`. | -| `ALPHA_VANTAGE_EARNINGS_HORIZON` | `3month` | Alpha Vantage earnings horizon: `3month`, `6month`, or `12month`. | -| `ALPHA_VANTAGE_EARNINGS_SYMBOL` | empty | Optional single-symbol Alpha Vantage earnings query; empty fetches the full scheduled earnings list. | -| `REFDATA_EVENT_CALENDAR_REFRESH_MS` | `86400000` | Refdata refresh cadence for provider-backed event-calendar cache writes. | +| `DARK_INFER_COOLDOWN_MS` | `30000` | Cooldown before repeated dark inferences for same symbol/pattern. | +| `SMART_MONEY_EVENT_CALENDAR_PATH` | empty | Optional JSON event-calendar file used by compute. | +| `REFDATA_EVENT_CALENDAR_PATH` | empty | Optional JSON event-calendar path for refdata; falls back to `SMART_MONEY_EVENT_CALENDAR_PATH`. | +| `REFDATA_EVENT_CALENDAR_PROVIDER` | empty | Set to `alpha_vantage` to refresh event-calendar cache from Alpha Vantage. | +| `ALPHA_VANTAGE_API_KEY` | empty | Alpha Vantage key for provider-backed event-calendar refresh. | -Event-calendar rows may use `symbol`, `underlying`, or `underlying_id`; `event_date`, `event_time`, or `event_ts`; and `announced_ts`, `available_ts`, `as_of_ts`, or `created_ts`. Compute only uses events already available at the packet timestamp, so missing or unavailable rows leave event-alignment features as neutral `null` values. - -### Candle service configuration - -| Variable | Default | What it controls | -| --- | --- | --- | -| `CANDLE_INTERVALS_MS` | `60000,300000` | Comma-separated candle intervals generated from equity prints. | -| `CANDLE_MAX_LATE_MS` | `0` | Allowed lateness for out-of-order prints before candle rejection/roll policy applies. | -| `CANDLE_CACHE_LIMIT` | `2000` | Max cached candles per `(underlying, interval)` in Redis (`0` disables cache). | -| `CANDLE_DELIVER_POLICY` | `new` | Consumer start policy for candle service (`new`, `all`, `last`, `last_per_subject`). | -| `CANDLE_CONSUMER_RESET` | `false` | If true, resets candle durable consumer position on startup. | - -### API + live cache configuration +### API, live cache, and web client | Variable | Default | What it controls | | --- | --- | --- | | `API_PORT` | `4000` | API service listen port. | -| `REST_DEFAULT_LIMIT` | `200` | Default record count when a REST endpoint omits `limit`. | -| `API_DELIVER_POLICY` | `new` | JetStream consumer start policy used by API live subscribers (`new`, `all`, `last`, `last_per_subject`). | -| `API_CONSUMER_RESET` | `false` | If true, API resets/recreates its live durable consumers on startup. | -| `LIVE_LIMIT_OPTIONS` | `10000` | In-memory/Redis live cache depth for options channel (clamped `1..100000`). | -| `LIVE_LIMIT_NBBO` | `10000` | Live cache depth for options NBBO channel (clamped `1..100000`). | -| `LIVE_LIMIT_EQUITIES` | `10000` | Live cache depth for equities channel (clamped `1..100000`). | -| `LIVE_LIMIT_EQUITY_QUOTES` | `10000` | Live cache depth for equity quotes channel (clamped `1..100000`). | -| `LIVE_LIMIT_EQUITY_JOINS` | `10000` | Live cache depth for equity join channel (clamped `1..100000`). | -| `LIVE_LIMIT_FLOW` | `10000` | Live cache depth for flow packet channel (clamped `1..100000`). | -| `LIVE_LIMIT_CLASSIFIER_HITS` | `10000` | Live cache depth for classifier hits channel (clamped `1..100000`). | -| `LIVE_LIMIT_ALERTS` | `10000` | Live cache depth for alerts channel (clamped `1..100000`). | -| `LIVE_LIMIT_INFERRED_DARK` | `10000` | Live cache depth for inferred dark channel (clamped `1..100000`). | - -### Web client configuration (`NEXT_PUBLIC_*`) - -| Variable | Default | What it controls | -| --- | --- | --- | -| `NEXT_PUBLIC_API_URL` | auto-detected (`window.location.origin` in browser; `http://127.0.0.1:4000` fallback) | Explicit base URL for API/WS calls from the web app. | -| `NEXT_PUBLIC_LIVE_HOT_WINDOW` | `2000` | Max hot-window items retained for non-options live streams in UI state (`100..100000`). | -| `NEXT_PUBLIC_LIVE_HOT_WINDOW_OPTIONS` | `25000` | Dedicated max hot-window items retained for options prints (`100..100000`). | -| `NEXT_PUBLIC_NBBO_MAX_AGE_MS` | `1000` | Frontend NBBO staleness threshold used for UI status/placement logic. | -| `NEXT_PUBLIC_LIVE_EQUITIES_SILENT_WARNING_MS` | `25000` | Delay before warning when equities stream is quiet (`5000..300000`). | -| `NEXT_PUBLIC_PINNED_EVIDENCE_TTL_MS` | `1200000` | TTL for pinned evidence objects in UI (`60000..7200000`). | -| `NEXT_PUBLIC_PINNED_EVIDENCE_MAX_ITEMS` | `4000` | Maximum pinned evidence cache size in UI (`100..50000`). | -| `NEXT_PUBLIC_FLOW_FILTER_PRESET` | `smart-money` | Default flow filter preset applied on page load (`smart-money`, `balanced`, `all`). | +| `REST_DEFAULT_LIMIT` | `200` | Default REST record count. | +| `API_DELIVER_POLICY` | `new` | JetStream consumer start policy used by API live subscribers. | +| `API_CONSUMER_RESET` | `false` | Resets/recreates API live durable consumers on startup when true. | +| `LIVE_LIMIT_DEFAULT` | `1000` | Optional generic live cache depth default. | +| `LIVE_LIMIT_FLOW` | `500` | Live cache depth for flow packet events unless overridden. | +| `LIVE_LIMIT_SMART_MONEY` | `300` | Live cache depth for smart-money events unless overridden. | +| `LIVE_LIMIT_OPTIONS` | `1000` | Live cache depth for options channel unless overridden. | +| `LIVE_LIMIT_ALERTS` | `300` | Live cache depth for alerts channel unless overridden. | +| `LIVE_LIMIT_NEWS` | `100` | Live cache depth for news channel unless overridden. | +| `NEXT_PUBLIC_API_URL` | auto-detected in browser, `http://127.0.0.1:4000` fallback | Explicit base URL for API/WS calls from the web app. | +| `NEXT_PUBLIC_LIVE_HOT_WINDOW` | `600` | Max hot-window items retained for non-options live streams in UI state. | +| `NEXT_PUBLIC_LIVE_HOT_WINDOW_OPTIONS` | `1200` | Dedicated max hot-window items retained for options prints. | +| `NEXT_PUBLIC_NBBO_MAX_AGE_MS` | `1000` | Frontend NBBO staleness threshold. | +| `NEXT_PUBLIC_FLOW_FILTER_PRESET` | `smart-money` | Default flow filter preset: `smart-money`, `balanced`, or `all`. | ### Replay and testing controls | Variable | Default | What it controls | | --- | --- | --- | -| `REPLAY_ENABLED` | `false` | Dev-script toggle: starts replay service in `bun run dev` when truthy. | -| `REPLAY_STREAMS` | `options,nbbo,equities,equity-quotes` | Replay stream selection (`all` or comma list of supported aliases). | -| `REPLAY_START_TS` | `0` | Replay lower-bound timestamp; `0` means from earliest stored data. | -| `REPLAY_END_TS` | `0` | Replay upper-bound timestamp; `0` means no explicit end bound. | -| `REPLAY_SPEED` | `1` | Replay speed multiplier relative to original event timing. | -| `REPLAY_BATCH_SIZE` | `200` | Batch fetch size per replay stream pull. | -| `REPLAY_LOG_EVERY` | `1000` | Progress log interval (emitted event count). | +| `REPLAY_ENABLED` | `false` | Starts replay service in `bun run dev` when truthy. | +| `REPLAY_STREAMS` | `options,nbbo,equities,equity-quotes` | Replay stream selection. | +| `REPLAY_START_TS` | `0` | Replay lower-bound timestamp. | +| `REPLAY_END_TS` | `0` | Replay upper-bound timestamp. | +| `REPLAY_SPEED` | `1` | Replay speed multiplier. | +| `REPLAY_BATCH_SIZE` | `200` | Batch fetch size per stream. | +| `REPLAY_LOG_EVERY` | `1000` | Progress log interval. | | `TESTING_MODE` | `false` | Enables ingest publish throttling for deterministic/lower-volume test runs. | | `TESTING_THROTTLE_MS` | `200` | Minimum delay between emitted events while `TESTING_MODE=true`. | ## Quick Notes -- Python dependencies are required only for IBKR/Databento sidecars (`services/ingest-options/py/requirements.txt`). +- Python dependencies are required only for IBKR/Databento sidecars: `services/ingest-options/py/requirements.txt`. - Candle construction is server-side; the client consumes prebuilt OHLC events. -- Option prints now persist as enriched raw rows and can be queried as either: - - `view=signal` — default live/UI path and compute input. - - `view=raw` — audit/debug path that preserves every stored print. -- The default Tape page options/packets posture is now stock-only, hides `B` / `BB`, keeps calls and puts visible, and applies in-memory min-notional controls immediately. -- Live retention uses a two-tier model: - - ClickHouse is durable server history; Redis is a bounded hot cache per live generic channel. - - `LIVE_LIMIT_*` controls initial snapshot/hot-cache depth, not total persisted history. - - Browser state is only a rendering window and UI preferences, not a market-data database. - - Devices connected to the same API hydrate from the same server-seen history. - - UI keeps a bounded hot window for rendering performance around the signal view rather than raw noise. - - Options prints can use a deeper dedicated cap via `NEXT_PUBLIC_LIVE_HOT_WINDOW_OPTIONS` without raising every other feed. - - Alert/drawer evidence is pinned and hydrated by id/trace so details remain inspectable after hot-window eviction. -- Firehose-readiness strategy: - - preserve raw ingest for storage/replay, - - feed compute and default live UI from the filtered signal path, - - add filterable live subscription contracts now so selective delivery can move server-side without reshaping the protocol later. +- Option prints persist as enriched raw rows and can be queried as `view=signal` or `view=raw`. +- The default Tape page options/packets posture is stock-only, hides `B` / `BB`, keeps calls and puts visible, and applies in-memory min-notional controls immediately. +- Live retention uses ClickHouse for durable server history, Redis for bounded hot cache, and browser state for rendering windows/preferences. +- Alert and drawer evidence is pinned and hydrated by id/trace so details remain inspectable after hot-window eviction. +- Firehose readiness keeps raw ingest for storage/replay, routes default compute/UI through filtered signals, and keeps subscription contracts ready for server-side selective delivery. - This repository is for personal, non-redistributed usage. ## Useful Examples diff --git a/docs/turns/2026-05-19-0739-update-readme-current-state.html b/docs/turns/2026-05-19-0739-update-readme-current-state.html new file mode 100644 index 0000000..77e0a2a --- /dev/null +++ b/docs/turns/2026-05-19-0739-update-readme-current-state.html @@ -0,0 +1,259 @@ + + + + + + README Current-State Update + + + +
+
+
Turn document · 2026-05-19 07:39 America/New_York
+

README Current-State Update

+

+ Resolved the README merge conflict and rewrote the project overview so it matches the current Islandflow codebase, including the smart-money taxonomy, Next.js 16 update, news ingest, desktop shell, and current deployment posture. +

+
+ README.md + smart-money taxonomy + Next.js 16.2.6 + deployment docs +
+
+ +
+

Summary

+

+ The README no longer contains conflict markers. It now gives a concise but current description of the platform, its runtime services, public smart-money categories, environment knobs, and supported deployment workflow. +

+
+ +
+

Changes Made

+
    +
  • Resolved the conflicted README by preserving the useful project-state content and removing stale simplified sections.
  • +
  • Added a first-class smart-money taxonomy section for the six public profiles: institutional_directional, retail_whale, event_driven, vol_seller, arbitrage, and hedge_reactive.
  • +
  • Documented that smart-money events are now the semantic object, while legacy classifier hits and alerts remain compatibility surfaces.
  • +
  • Updated the current implementation state to include Alpaca news ingest, profile-aware UI behavior, alert-context hydration, and the Electron shell.
  • +
  • Recorded the Next.js update to 16.2.6 with React and React DOM 19.2.0.
  • +
  • Clarified deployment: Docker is still the supported VPS path, native Bun/systemd rollout is experimental, and scoped deploy flags are available.
  • +
  • Aligned live-cache and web hot-window defaults with the current env examples and API defaults.
  • +
+
+ +
+

Context

+

+ Recent commits showed the README branch was carrying a Next.js upgrade, Alpaca news support, smart-money event work, and deployment helper changes. The prior README mixed both sides of a merge conflict and did not explain the newer taxonomy-driven classifier model. +

+
+ +
+

Important Implementation Details

+

+ The README intentionally treats FlowPacket as an intermediate clustering bridge and SmartMoneyEvent as the current semantic surface. It also documents abstention and suppression behavior so readers do not mistake every large print for a forced smart-money label. +

+

+ Deployment language now matches the current operations docs: ./deploy main defaults to the Docker path, --runtime native is available but experimental, and native rollout still depends on systemd units and reverse-proxy preparation. +

+
+ +
+

Relevant Diff Snippets

+

+ Diff snippets are formatted for readability in the same spirit as diffs.com, with only the most relevant README changes shown here. +

+
+## Smart-Money Classification Taxonomy
++
++Islandflow now emits first-class `SmartMoneyEvent` records instead of treating old classifier hits as the final semantic object.
++
++| Profile ID | Meaning | Common evidence |
++| --- | --- | --- |
++| `institutional_directional` | Large directional parent flow with stronger institutional-style conviction. | premium, size, sweep/burst behavior, aggressor imbalance, quote quality |
++| `retail_whale` | Large retail-style speculative bursts, often short-dated or attention-driven. | short-dated OTM concentration, burst prints, IV shock |
++| `event_driven` | Flow aligned to known upcoming events. | event-calendar proximity, expiry after event, pre-event concentration |
++| `vol_seller` | Premium-selling or short-volatility structure evidence. | sell-side premium, straddles/strangles |
++| `arbitrage` | Multi-leg or symmetric structures with low directional exposure. | matched leg symmetry, near-flat directional bias |
++| `hedge_reactive` | Hedge or dealer-reaction style flow around short-dated ATM/gamma context. | 0-2 DTE, near-ATM contracts, underlying move linkage |
+
+## Deployment Workflow
++
++Docker remains the supported and recommended path for the current VPS.
++
++./deploy main
++./deploy main --runtime docker
++./deploy current-branch
++./deploy current-branch --runtime docker
++
++Native deployment is opt-in and experimental:
++
++./deploy main --runtime native
++./deploy current-branch --runtime native
+
+ +
+

Expected Impact for End-Users

+

+ New contributors or future sessions should be able to read the README and understand what Islandflow currently does, which service owns each capability, how the smart-money labels should be interpreted, and which deployment command is appropriate for the VPS. +

+
+ +
+

Validation

+
    +
  • Confirmed no merge conflict markers remain with rg -n "<<<<<<<|=======|>>>>>>>" README.md.
  • +
  • Ran git diff --check; no whitespace or patch-format issues were reported.
  • +
  • Ran focused tests: bun test packages/types/tests/options-flow.test.ts packages/types/tests/live.test.ts packages/storage/tests/smart-money-events.test.ts services/compute/tests/parent-events.test.ts.
  • +
  • Focused test result: 12 pass, 0 fail.
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+
    +
  • This was documentation-only, so no full production web build was run. The focused tests cover the smart-money/type/storage claims most relevant to the README update.
  • +
  • The README summarizes environment variables instead of listing every low-level classifier and dark-inference threshold. Detailed knobs remain available in .env.example and service code.
  • +
  • Native deployment remains experimental; the README calls that out directly and points to the dedicated native deployment document.
  • +
+
+ +
+

Follow-up Work

+
    +
  • islandflow-38p: add native deployment unit templates and rollback helpers.
  • +
  • islandflow-932: continue desktop follow-up native features.
  • +
  • islandflow-2db: manually remove stale local-infra containers from the VPS when doing server hygiene.
  • +
+
+
+ + From a790a2815cd23ab7f7b08b719d56a47b6ef69ed8 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Tue, 19 May 2026 08:05:30 -0400 Subject: [PATCH 08/12] clarify repo turn documentation scope --- .beads/issues.jsonl | 1 + AGENTS.md | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 61aef8b..40c5966 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -13,6 +13,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-lm6","title":"Clarify repo turn documentation scope","description":"Update AGENTS.md so repository turn documentation clearly uses repo-local docs/turns and impeccable styling, without inheriting global non-repo computer-task styling.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-19T12:05:07Z","created_by":"dirtydishes","updated_at":"2026-05-19T12:05:07Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-6iq","title":"Update README for current project state","description":"Resolve README merge conflicts and document the current project state, including the smart money classification taxonomy, Next.js update, and deployment workflow changes.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:37:24Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:40:01Z","started_at":"2026-05-19T11:37:31Z","closed_at":"2026-05-19T11:40:01Z","close_reason":"README conflict resolved and current project state documented, including smart-money taxonomy, Next.js update, and deployment workflow.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-lib","title":"Upgrade apps/web to Next.js 16.2.6","description":"Upgrade the web app dependency stack to Next.js 16.2.6 with React 19, refresh Bun and mirrored Docker workspace lockfiles, keep runtime behavior unchanged, fix any focused web test fallout, validate the web build and targeted route tests, and document the completed work.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:04:51Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:31:23Z","started_at":"2026-05-19T11:04:57Z","closed_at":"2026-05-19T11:31:23Z","close_reason":"Upgraded apps/web to Next.js 16.2.6 with React 19, refreshed Bun lockfiles including the Docker workspace mirror, fixed the React 19 nullable ref type issue, and validated the web build, focused tests, Docker workspace sync, and route smoke checks.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-8fn","title":"implement alpaca-backed news wire view","description":"Why this issue exists and what needs to be done:\\nAdd an Alpaca-powered live news pipeline, API, storage, and web experience, including a dedicated /news route, Home preview, live fanout, history pagination, ticker resolution, and replay-mode live-only empty states.\\n\\nAcceptance criteria:\\n- normalized NewsStory contract and live channel exist\\n- ingest-news service backfills and streams Alpaca news\\n- API persists, serves, and fans out news\\n- web app exposes /news plus Home preview and drawer\\n- tests cover types, storage, API, and key UI behaviors\\n- turn documentation is added\\n\\nDesign:\\nReuse Islandflow drawer, chips, panes, and terminal styling; keep news live-only in v1 replay mode.\\n\\nNotes:\\nImplement client-side ticker filtering in v1 and expose latest revision only per provider+story_id.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T20:37:13Z","created_by":"dirtydishes","updated_at":"2026-05-18T20:55:11Z","started_at":"2026-05-18T20:37:20Z","closed_at":"2026-05-18T20:55:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/AGENTS.md b/AGENTS.md index 08a484a..fe8ffca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,7 +82,6 @@ Save the document in: ```text docs/turns/ ``` -## Important: If you are not working inside a git repository, save the document to `~/dev/docs/turns/` Use a clear timestamped filename: @@ -98,9 +97,11 @@ docs/turns/2026-05-14-add-market-replay-controls.html ### Format -Use the impeccable skill to structure the document as clean, readable HTML. +Use the `impeccable` skill to structure and style the document as clean, readable HTML. -If the impeccable skill is unavailable, still create a well-structured standalone HTML file with: +For this repository, `impeccable` is the styling and layout authority for turn documents when available. Do not apply global non-repo computer-task house styling to repository turn documents. + +If the `impeccable` skill is unavailable or blocked by an actual tool/file error, still create a well-structured standalone HTML file with: - A concise summary at the top - A detailed explanation of what changed From cb2de93ddec07ad9932120eca28fa214b9ded48b Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Tue, 19 May 2026 08:06:10 -0400 Subject: [PATCH 09/12] clarify repo turn doc rules --- ...5-19-0805-clarify-repo-turn-doc-rules.html | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 docs/turns/2026-05-19-0805-clarify-repo-turn-doc-rules.html diff --git a/docs/turns/2026-05-19-0805-clarify-repo-turn-doc-rules.html b/docs/turns/2026-05-19-0805-clarify-repo-turn-doc-rules.html new file mode 100644 index 0000000..9342851 --- /dev/null +++ b/docs/turns/2026-05-19-0805-clarify-repo-turn-doc-rules.html @@ -0,0 +1,200 @@ + + + + + + Clarify Repo Turn Documentation Rules + + + +
+
+
Turn document · 2026-05-19 08:05 America/New_York
+

Clarify Repo Turn Documentation Rules

+

+ Updated the repository instructions so Islandflow turn documents are clearly repo-local and styled through impeccable, without inheriting global non-repo computer-task styling. +

+
+ +
+

Summary

+

+ The repo AGENTS.md now removes a stray non-repo location rule and explicitly states that impeccable is the styling and layout authority for Islandflow turn documents when available. +

+
+ +
+

Changes Made

+
    +
  • Removed the confusing instruction to save non-repo documentation under ~/dev/docs/turns/.
  • +
  • Clarified that repository turn documents stay in docs/turns/.
  • +
  • Updated the format rule to say impeccable handles both structure and styling.
  • +
  • Added an explicit guard against applying global non-repo computer-task house styling to this repository's turn documents.
  • +
  • Clarified that the fallback standalone HTML path only applies when impeccable is unavailable or blocked by an actual error.
  • +
+
+ +
+

Context

+

+ The global agent instructions now distinguish repository implementation documentation from non-repo computer-task documentation. This repo file needed a small cleanup so it would not reintroduce ambiguity about location or styling. +

+
+ +
+

Important Implementation Details

+

+ This was a documentation-only change in AGENTS.md. It changes future agent behavior but does not alter runtime code, tests, deployment scripts, or application behavior. +

+
+ +
+

Relevant Diff Snippets

+
-## Important: If you are not working inside a git repository, save the document to `~/dev/docs/turns/`
+
+-Use the impeccable skill to structure the document as clean, readable HTML.
++Use the `impeccable` skill to structure and style the document as clean, readable HTML.
++
++For this repository, `impeccable` is the styling and layout authority for turn documents when available. Do not apply global non-repo computer-task house styling to repository turn documents.
+
+-If the impeccable skill is unavailable, still create a well-structured standalone HTML file with:
++If the `impeccable` skill is unavailable or blocked by an actual tool/file error, still create a well-structured standalone HTML file with:
+
+ +
+

Expected Impact for End-Users

+

+ Future Islandflow turns should produce documentation in the repo's docs/turns/ folder and let impeccable drive the visual treatment, making repo documentation less likely to inherit global computer-task styling. +

+
+ +
+

Validation

+
    +
  • Reviewed the AGENTS.md diff after patching.
  • +
  • Ran git diff --check with no whitespace errors.
  • +
  • No application test suite was run because this change only updates repository instructions.
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+

+ This clarification depends on future agents reading both global and repo instructions. The new wording is intentionally direct about repo scope, location, and styling to reduce that risk. +

+
+ +
+

Follow-up Work

+

+ No follow-up issue is required for this patch. The related Beads task for this documentation cleanup is islandflow-lm6. +

+
+
+ + From 328974b374a130e2e31503189322763c396f28a8 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Tue, 19 May 2026 08:06:33 -0400 Subject: [PATCH 10/12] update beads for repo doc rules --- .beads/issues.jsonl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 40c5966..aa74dd2 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -13,7 +13,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-lm6","title":"Clarify repo turn documentation scope","description":"Update AGENTS.md so repository turn documentation clearly uses repo-local docs/turns and impeccable styling, without inheriting global non-repo computer-task styling.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-19T12:05:07Z","created_by":"dirtydishes","updated_at":"2026-05-19T12:05:07Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-lm6","title":"Clarify repo turn documentation scope","description":"Update AGENTS.md so repository turn documentation clearly uses repo-local docs/turns and impeccable styling, without inheriting global non-repo computer-task styling.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T12:05:07Z","created_by":"dirtydishes","updated_at":"2026-05-19T12:06:12Z","started_at":"2026-05-19T12:05:14Z","closed_at":"2026-05-19T12:06:12Z","close_reason":"Verified AGENTS.md now scopes repo turn docs to docs/turns and makes impeccable the styling authority; added turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-6iq","title":"Update README for current project state","description":"Resolve README merge conflicts and document the current project state, including the smart money classification taxonomy, Next.js update, and deployment workflow changes.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:37:24Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:40:01Z","started_at":"2026-05-19T11:37:31Z","closed_at":"2026-05-19T11:40:01Z","close_reason":"README conflict resolved and current project state documented, including smart-money taxonomy, Next.js update, and deployment workflow.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-lib","title":"Upgrade apps/web to Next.js 16.2.6","description":"Upgrade the web app dependency stack to Next.js 16.2.6 with React 19, refresh Bun and mirrored Docker workspace lockfiles, keep runtime behavior unchanged, fix any focused web test fallout, validate the web build and targeted route tests, and document the completed work.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T11:04:51Z","created_by":"dirtydishes","updated_at":"2026-05-19T11:31:23Z","started_at":"2026-05-19T11:04:57Z","closed_at":"2026-05-19T11:31:23Z","close_reason":"Upgraded apps/web to Next.js 16.2.6 with React 19, refreshed Bun lockfiles including the Docker workspace mirror, fixed the React 19 nullable ref type issue, and validated the web build, focused tests, Docker workspace sync, and route smoke checks.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-8fn","title":"implement alpaca-backed news wire view","description":"Why this issue exists and what needs to be done:\\nAdd an Alpaca-powered live news pipeline, API, storage, and web experience, including a dedicated /news route, Home preview, live fanout, history pagination, ticker resolution, and replay-mode live-only empty states.\\n\\nAcceptance criteria:\\n- normalized NewsStory contract and live channel exist\\n- ingest-news service backfills and streams Alpaca news\\n- API persists, serves, and fans out news\\n- web app exposes /news plus Home preview and drawer\\n- tests cover types, storage, API, and key UI behaviors\\n- turn documentation is added\\n\\nDesign:\\nReuse Islandflow drawer, chips, panes, and terminal styling; keep news live-only in v1 replay mode.\\n\\nNotes:\\nImplement client-side ticker filtering in v1 and expose latest revision only per provider+story_id.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T20:37:13Z","created_by":"dirtydishes","updated_at":"2026-05-18T20:55:11Z","started_at":"2026-05-18T20:37:20Z","closed_at":"2026-05-18T20:55:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} From 75ff4f489f67b54fb4dfa15125d77554a650e4a4 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Tue, 19 May 2026 14:42:56 -0400 Subject: [PATCH 11/12] docs(daily-git): add 2026-05-18 standup summary --- .beads/issues.jsonl | 1 + ...2026-05-19-standup-summary-2026-05-18.html | 566 ++++++++++++++++++ 2 files changed, 567 insertions(+) create mode 100644 docs/daily-git/2026-05-19-standup-summary-2026-05-18.html diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index aa74dd2..c61c799 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -52,6 +52,7 @@ {"_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":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-2df","title":"Publish 2026-05-18 git standup summary","description":"Why: the daily automation needs a grounded standup summary for May 18, 2026. What: review commits from 2026-05-18, create a scannable HTML summary in docs/daily-git, and capture only commit/file-backed statements.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T18:41:07Z","created_by":"dirtydishes","updated_at":"2026-05-19T18:42:42Z","started_at":"2026-05-19T18:41:10Z","closed_at":"2026-05-19T18:42:42Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-x70","title":"Create 2026-05-17 git standup summary","description":"Why this issue exists and what needs to be done:\\n- Produce the daily automation summary for 2026-05-17 git activity.\\n- Ground statements in commits, PRs, and touched files only.\\n- Create a user-readable HTML document in docs/general and update automation memory.\\n- Complete the Beads sync and git push workflow after documenting the run.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T13:01:43Z","created_by":"dirtydishes","updated_at":"2026-05-18T13:05:37Z","started_at":"2026-05-18T13:01:53Z","closed_at":"2026-05-18T13:05:37Z","close_reason":"Closed","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} diff --git a/docs/daily-git/2026-05-19-standup-summary-2026-05-18.html b/docs/daily-git/2026-05-19-standup-summary-2026-05-18.html new file mode 100644 index 0000000..5a33e66 --- /dev/null +++ b/docs/daily-git/2026-05-19-standup-summary-2026-05-18.html @@ -0,0 +1,566 @@ + + + + + + Daily Git Summary for 2026-05-18 + + + +
+
+ Daily Git Summary +

Standup summary for Monday, May 18, 2026

+

+ Git history for May 18 shows four commits. One feature commit introduced an Alpaca-backed news wire across ingest, + storage, API, and web surfaces; the other three commits updated workflow docs, beads state, and the previous + standup summary. +

+
+
+ Commits +
4
+
+
+ Files Touched +
35
+
+
+ Insertions +
1963
+
+
+ Deletions +
52
+
+
+
+ +
+

Summary

+
+
+ Primary Delivery +

+ Commit 906fe411 added a new services/ingest-news service, news persistence in + packages/storage, API endpoints in services/api, and a live news view in + apps/web/app/terminal.tsx plus apps/web/app/news/page.tsx. +

+
+
+ Docs And Workflow +

+ Commits 62aae708, 687a2170, and 04baeceb updated the previous standup + report, beads state, deployment/docker/workspace-root/package.json, and the repo-level + AGENTS.md instructions. +

+
+
+ Standup Framing +

+ Yesterday’s visible product work centered on making live Alpaca news available end to end. The remaining + activity was project hygiene and documentation. +

+
+
+
+ +
+

Changes Made

+
+
+
+ update beads + 687a2170 + 2026-05-18 03:15 -0400 + 1 file +
+

+ Added one line to deployment/docker/workspace-root/package.json. The local git history does not + show more context beyond the file touch and commit subject. +

+
+ +
+
+ docs(general): add 2026-05-17 standup summary + 62aae708 + 2026-05-18 09:05 -0400 + 2 files +
+

+ Added the prior day’s report at docs/general/2026-05-18-standup-summary-2026-05-17.html and + updated .beads/issues.jsonl. +

+
+ docs/general/2026-05-18-standup-summary-2026-05-17.html + .beads/issues.jsonl +
+
+ +
+
+ add alpaca news wire across ingest api and web + 906fe411 + 2026-05-18 16:55 -0400 + 31 files + +1407 / -50 +
+
    +
  • + Added a new ingest service in services/ingest-news/src/index.ts that backfills Alpaca news, + subscribes to the Alpaca news websocket, resolves symbols, and publishes NewsStory payloads to + NATS. +
  • +
  • + Extended shared contracts in packages/types/src/events.ts and + packages/types/src/live.ts, plus new storage support in + packages/storage/src/news.ts and packages/storage/src/clickhouse.ts. +
  • +
  • + Wired the API to store, fan out, and expose news via /news and /history/news in + services/api/src/index.ts and live-session updates in services/api/src/live.ts. +
  • +
  • + Added a web route in apps/web/app/news/page.tsx, a news pane and drawer in + apps/web/app/terminal.tsx, and related styling in apps/web/app/globals.css. +
  • +
  • + Updated runtime packaging and local/dev deployment surfaces, including + deployment/docker/docker-compose.yml, Dockerfiles, scripts/dev.ts, and + scripts/deploy.ts. +
  • +
  • + Added tests in packages/storage/tests/news.test.ts, + services/ingest-news/tests/symbols.test.ts, and adjusted + apps/web/app/terminal.test.ts plus packages/types/tests/live.test.ts. +
  • +
+
+ services/ingest-news/src/index.ts + packages/storage/src/news.ts + services/api/src/index.ts + apps/web/app/terminal.tsx + apps/web/app/news/page.tsx + apps/web/app/globals.css +
+
+ +
+
+ update turn docs and beads workflow + 04baeceb + 2026-05-18 21:32 -0400 + 1 file +
+

+ Edited AGENTS.md to update turn-document and beads workflow guidance. +

+
+
+
+ +
+

Context

+

+ This summary is based on local git history between 2026-05-18 00:00 -0400 and + 2026-05-19 00:00 -0400. The repository uses Bun, TypeScript, NATS/JetStream, ClickHouse, and a Next.js + web app, so the main feature commit spans service ingestion, shared types, persistence, API delivery, and the UI. +

+
+ +
+

Important Implementation Details

+
+
+

News ingestion was introduced as a first-class service

+

+ services/ingest-news/src/index.ts authenticates against Alpaca, backfills recent news, subscribes + to live updates, resolves symbols, validates payloads with NewsStorySchema, and publishes them onto + the repo’s bus layer. +

+
const backfill = await fetchBackfill();
+for (const item of backfill.reverse()) {
+  await publishStory(item);
+}
+
+if (msg === "authenticated") {
+  ws.send(JSON.stringify({ action: "subscribe", news: ["*"] }));
+}
+
+ +
+

API and live session support were expanded for news

+

+ services/api/src/index.ts now ensures the news table exists, subscribes to a news consumer, fans + out live updates, and exposes both recent and paginated history endpoints. +

+
if (req.method === "GET" && url.pathname === "/news") {
+  const limit = parseLimit(url.searchParams.get("limit") ?? "100");
+  const data = await fetchRecentNews(clickhouse, limit);
+  return jsonResponse({ data });
+}
+
+ +
+

The web terminal gained a dedicated news surface

+

+ apps/web/app/terminal.tsx added a live-only news pane, a per-story drawer, history loading, and a + new /news route entry point via apps/web/app/news/page.tsx. +

+
if (features.news) {
+  subscriptions.push({ channel: "news", snapshot_limit: LIVE_OPTIONS_HEAD_LIMIT });
+}
+
+export function NewsRoute() {
+  const state = useTerminal();
+  return (
+    <PageFrame title="News">
+      <div className="page-grid page-grid-news">
+        <NewsPane state={state} className="news-pane-full" />
+      </div>
+    </PageFrame>
+  );
+}
+
+
+
+ +
+

Expected Impact for End-Users

+
+
+ Live Terminal +

+ Users now have a dedicated news wire surface in the web terminal, including summary rows, story details, and + a direct link to the source article. +

+
+
+ Coverage +

+ News is now available alongside the repo’s existing live feeds, with shared symbol resolution and storage that + make the data retrievable through API history endpoints. +

+
+
+ Current Boundary +

+ The UI copy in the news pane explicitly marks news as live-only in v1, so replay users should not expect the + same behavior there yet. +

+
+
+
+ +
+

Validation

+
    +
  • Reviewed local git history with git log --since='2026-05-18 00:00' --until='2026-05-19 00:00'.
  • +
  • Used git log --stat, git show, and file-level history to anchor each summary item to specific commits and files.
  • +
  • No builds or tests were run for this reporting task because the work product is a git summary document, not a behavior change.
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+
    +
  • + This report is grounded in local commit metadata only. No pull request identifiers were present in the inspected + git history, so the summary references commits and files instead of PR numbers. +
  • +
  • + The update beads commit touched only deployment/docker/workspace-root/package.json in + visible git output, so this report does not infer intent beyond that recorded file change. +
  • +
  • + Counts here describe May 18 commits only and exclude any uncommitted work present after that date. +
  • +
+
+ +
+

Follow-up Work

+
    +
  • + No new product follow-up items were derived from this reporting pass. The only beads item created for this task + is islandflow-2df, which tracks publication of this summary document. +
  • +
+
+
+ + From 8d39fb72a456bd43a5b9187f5f98a19e2ca33057 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Tue, 19 May 2026 14:45:06 -0400 Subject: [PATCH 12/12] track pr conflict reconciliation --- .beads/issues.jsonl | 1 + 1 file changed, 1 insertion(+) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index c61c799..67dab61 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,3 +1,4 @@ +{"_type":"issue","id":"islandflow-g3a","title":"Reconcile PR merge conflicts","description":"Resolve the current pull request conflicts for the nextjs-upgrade branch, validate the result, document the turn, and push the reconciled branch.","status":"in_progress","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-19T18:44:51Z","created_by":"dirtydishes","updated_at":"2026-05-19T18:44:56Z","started_at":"2026-05-19T18:44:56Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_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}