Compare commits
14 commits
8f0794ddf8
...
b70b8f0fe7
| Author | SHA1 | Date | |
|---|---|---|---|
| b70b8f0fe7 | |||
| 171cf52518 | |||
| 8d39fb72a4 | |||
| 75ff4f489f | |||
| 328974b374 | |||
| cb2de93dde | |||
| a790a2815c | |||
| 82fd29f1a4 | |||
| b6fa2f0d17 | |||
| 728ca5569d | |||
| 8173b05c1c | |||
| 04baecebe0 | |||
| 906fe411c9 | |||
| 62aae70878 |
43 changed files with 4043 additions and 348 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
{"_type":"issue","id":"islandflow-9rc","title":"Implement native fast iterative deploy plan","description":"Implement the checked-in plan at plans/2026-05-18-native-fast-iterative-deploy-plan.md. Cover deploy-phase timing instrumentation, native deployment operational assets, deploy guardrails, validation/cutover documentation, and any required live VPS remediation that is safely actionable from this session. Track follow-up items separately if anything cannot be completed in-repo or on the live host.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T07:15:19Z","created_by":"dirtydishes","updated_at":"2026-05-18T07:34:03Z","started_at":"2026-05-18T07:15:25Z","closed_at":"2026-05-18T07:34:03Z","close_reason":"Implemented the native fast iterative deploy plan with deploy timing summaries, worker-only native fast mode, edge-cutover guardrails, local-on-server execution support, checked-in native ops assets, live audit findings, and turn documentation. Remaining cutover work is tracked in islandflow-vvw.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_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":"closed","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:47:35Z","started_at":"2026-05-19T18:44:56Z","closed_at":"2026-05-19T18:47:35Z","close_reason":"Merged forgejo/main into nextjs-upgrade, resolved README and Beads conflicts, updated JetStream retention tests, validated deploy help, Docker workspace sync, API/bus tests, and web build, and added turn documentation.","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-8kj","title":"Configure persistent beads Dolt remote on deltaisland server","description":"Install the beads and Dolt CLIs on the server, configure a persistent Dolt sync remote backed by the server-hosted Forgejo repository, verify refs/dolt/data publication, and document Nginx Proxy Manager / firewall considerations.","status":"closed","priority":1,"issue_type":"task","assignee":"delta","created_at":"2026-05-17T10:31:31Z","created_by":"delta","updated_at":"2026-05-17T10:37:47Z","started_at":"2026-05-17T10:32:16Z","closed_at":"2026-05-17T10:37:47Z","close_reason":"Installed bd and dolt on the server, configured the Forgejo-backed Dolt remote, published refs/dolt/data, and documented the setup.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-200","title":"Implement durable options tape history","description":"Implement the plan from docs/plans/2026-05-16-1711-durable-options-tape-history.html: durable ClickHouse-backed options history, signal/all prints view selection, preserved execution context, stale semantics limited to live health, reset runbook, tests, and turn documentation.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:21:30Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:26:51Z","started_at":"2026-05-16T21:21:33Z","closed_at":"2026-05-16T21:26:51Z","close_reason":"Implemented durable options tape history, signal/raw view selection, reset runbook, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-200","title":"Implement durable options tape history","description":"Implement the plan from docs/plans/2026-05-16-1711-durable-options-tape-history.html: durable ClickHouse-backed options history, signal/all prints view selection, preserved execution context, stale semantics limited to live health, reset runbook, tests, and turn documentation.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:21:30Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:26:51Z","started_at":"2026-05-16T21:21:33Z","closed_at":"2026-05-16T21:26:51Z","close_reason":"Implemented durable options tape history, signal/raw view selection, reset runbook, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-k4f","title":"Gate deploy script on docker workspace snapshot sync","description":"Prevent frozen-lockfile build failures during deploy by adding a local preflight in scripts/deploy.ts that runs bun run check:docker-workspace and aborts with a clear sync+commit remediation message when stale.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:01:44Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:04:11Z","started_at":"2026-05-15T23:01:48Z","closed_at":"2026-05-15T23:04:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-k4f","title":"Gate deploy script on docker workspace snapshot sync","description":"Prevent frozen-lockfile build failures during deploy by adding a local preflight in scripts/deploy.ts that runs bun run check:docker-workspace and aborts with a clear sync+commit remediation message when stale.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:01:44Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:04:11Z","started_at":"2026-05-15T23:01:48Z","closed_at":"2026-05-15T23:04:11Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|
@ -13,12 +14,20 @@
|
||||||
{"_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-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-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-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-fl5","title":"Decide final public posture for api.flow.deltaisland.io after native cutover","description":"Why this issue exists and what needs to be done:\\n- Native cutover now works end-to-end through Nginx Proxy Manager and the public API hostname now resolves directly to the VPS\\n- The API hostname was left DNS-only in Cloudflare during incident resolution, while the web hostname still uses the Cloudflare proxy\\n- We need to decide whether api.flow.deltaisland.io should remain direct-to-origin or be re-proxied through Cloudflare, then validate TLS, websocket, and operational behavior for the chosen posture","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-18T23:51:21Z","created_by":"dirtydishes","updated_at":"2026-05-18T23:51:21Z","dependencies":[{"issue_id":"islandflow-fl5","depends_on_id":"islandflow-vvw","type":"discovered-from","created_at":"2026-05-18T19:52:32Z","created_by":"dirtydishes","metadata":"{}"}],"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-vvw","title":"Stage native public-edge cutover after worker soak","description":"Why this issue exists and what needs to be done:\\n- The native deploy path is now provisioned for worker-first iteration, with checked-in user units, rollback helpers, and edge guardrails\\n- Remaining work is to enable and soak native worker units, validate duplicate-processing behavior, then deliberately cut over the public web/api edge if warranted\\n- Final acceptance should include deciding whether Docker or native becomes the default runtime after operational evidence","notes":"2026-05-18: native infra, native app services, NPM public-edge retargeting, Docker rollback helpers, and Cloudflare/DNS API hostname recovery were implemented and verified. Public checks now pass for flow.deltaisland.io and api.flow.deltaisland.io. Remaining follow-up: decide whether api.flow.deltaisland.io should remain DNS-only or be re-proxied through Cloudflare under islandflow-fl5.","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T07:32:35Z","created_by":"dirtydishes","updated_at":"2026-05-18T23:52:32Z","started_at":"2026-05-18T23:51:20Z","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-bsg","title":"Fix public /replay/options proxy regression","description":"Restore correct public routing for GET /replay/options on flow.deltaisland.io. The app currently serves HTML for that API path, which indicates edge/proxy routing drift. Update the live proxy topology or deployment assets as needed, then validate with bun run scripts/check-public-api-routes.ts.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-18T07:15:19Z","created_by":"dirtydishes","updated_at":"2026-05-18T07:32:51Z","started_at":"2026-05-18T07:15:24Z","closed_at":"2026-05-18T07:32:51Z","close_reason":"Audited the live VPS and reverse proxy on 2026-05-18: public /replay/options now returns JSON, bun run scripts/check-public-api-routes.ts passes, and the active Nginx Proxy Manager config includes /replay in the API route matcher. No in-repo app code change was required.","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-9j5","title":"Prepare PR for deploy allowlist cleanup","description":"Why this issue exists and what needs to be done:\\n- Package current deploy allowlist cleanup into a reviewable PR with multiple commits\\n- Add required turn documentation in docs/turns\\n- Run validation and push all artifacts","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T15:44:12Z","created_by":"dirtydishes","updated_at":"2026-05-17T15:53:55Z","started_at":"2026-05-17T15:44:22Z","closed_at":"2026-05-17T15:53:55Z","close_reason":"Packaged deploy allowlist cleanup into multi-commit PR branch with required turn documentation and push workflow.","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}
|
||||||
|
{"_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}
|
||||||
|
{"_type":"issue","id":"islandflow-4e9","title":"Polish terminal view","description":"Improve the Islandflow web terminal view with a focused UI polish pass aligned to the product design system.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T15:18:18Z","created_by":"dirtydishes","updated_at":"2026-05-17T15:25:02Z","started_at":"2026-05-17T15:18:21Z","closed_at":"2026-05-17T15:25:02Z","close_reason":"Polished terminal shell styling, responsive Tape actions, and documented the turn.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
{"_type":"issue","id":"islandflow-lyt","title":"Summarize 2026-05-16 git activity for standup","description":"Create a grounded standup summary for yesterday's git activity, anchored to commits, changed files, and any linked PR context if present. Produce the required HTML document in docs/general and complete the beads + git handoff workflow.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-17T14:02:57Z","created_by":"dirtydishes","updated_at":"2026-05-17T14:05:37Z","started_at":"2026-05-17T14:03:09Z","closed_at":"2026-05-17T14:05:37Z","close_reason":"Created docs/general standup summary for 2026-05-16 git activity, grounded to commits and changed files, and prepared the repo handoff workflow.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
{"_type":"issue","id":"islandflow-sz8","title":"Fix public /replay/options proxy regression","description":"## Summary\nThe new deploy-time public route checker added in commit 1424a27 (\"fix durable options history routing\") currently fails against https://flow.deltaisland.io because GET /replay/options returns HTML instead of JSON.\n\n## Evidence\n- `bun run scripts/check-public-api-routes.ts https://flow.deltaisland.io` fails on `/replay/options?view=signal\u0026after_ts=0\u0026after_seq=0\u0026limit=1` with `returned non-JSON content (text/html; charset=UTF-8)`\n- `services/api/src/index.ts` implements `GET /replay/options`, so the HTML response indicates the request is landing on the web app instead of the API service\n- `deployment/docker/README.md` documents that same-origin proxy mode must include `/replay/*` in the API route matcher\n\n## Minimal Fix\nUpdate the live reverse proxy / edge route matcher for flow.deltaisland.io so `/replay/*` is forwarded to the API host, then rerun `bun run check:public-api-routes`.\n\n## Notes\nThis looks like a production proxy configuration regression rather than an in-repo application bug.","status":"open","priority":2,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-05-17T13:06:11Z","created_by":"dirtydishes","updated_at":"2026-05-17T13:06:11Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","notes":"Implemented live scroll-hold with no live pause button, demand-loaded ClickHouse history, a 100-row options hot head, and cache-first scoped snapshots. Validated with bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts and bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:23:43Z","started_at":"2026-05-16T18:12:54Z","closed_at":"2026-05-16T18:23:43Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","notes":"Implemented live scroll-hold with no live pause button, demand-loaded ClickHouse history, a 100-row options hot head, and cache-first scoped snapshots. Validated with bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts and bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:23:43Z","started_at":"2026-05-16T18:12:54Z","closed_at":"2026-05-16T18:23:43Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-18T07:32:48Z","started_at":"2026-05-18T07:15:25Z","closed_at":"2026-05-18T07:32:48Z","close_reason":"Audited the live VPS on 2026-05-18: docker compose ls and container labels no longer show a duplicate islandflow compose project, so the stale local-infra stack cleanup appears to already be resolved on the host.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-c87","title":"Clean up duplicate Islandflow Docker infra on VPS","description":"The live VPS is currently running both the production-style islandflow-vps Docker stack and an older root-level islandflow infra stack that publishes NATS, ClickHouse, and Redis on host ports. Investigate whether the older stack is unused, remove it safely if so, and update docs/deploy guidance so the server topology is clearer.","notes":"Inspected the live VPS and confirmed the duplicate compose project: islandflow-vps is the supported deployment stack, while a separate islandflow project from the repo-root docker-compose.yml still runs exposed NATS/ClickHouse/Redis containers. Verified Nginx Proxy Manager routes only to islandflow-vps web/api by Docker name. Attempted cleanup via docker compose down and docker rm -f on the stale islandflow containers, but those commands hung for the delta user and timed out. Added repo guardrails and docs so deploy warns when the duplicate project exists, and opened islandflow-2db for manual host-level cleanup during a maintenance window.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:16:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:07Z","started_at":"2026-05-16T01:16:09Z","closed_at":"2026-05-16T01:28:07Z","close_reason":"Completed the repo-side investigation and guardrails. Actual server-side container removal is blocked by hanging Docker operations and is tracked separately in islandflow-2db for a maintenance window.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-c87","title":"Clean up duplicate Islandflow Docker infra on VPS","description":"The live VPS is currently running both the production-style islandflow-vps Docker stack and an older root-level islandflow infra stack that publishes NATS, ClickHouse, and Redis on host ports. Investigate whether the older stack is unused, remove it safely if so, and update docs/deploy guidance so the server topology is clearer.","notes":"Inspected the live VPS and confirmed the duplicate compose project: islandflow-vps is the supported deployment stack, while a separate islandflow project from the repo-root docker-compose.yml still runs exposed NATS/ClickHouse/Redis containers. Verified Nginx Proxy Manager routes only to islandflow-vps web/api by Docker name. Attempted cleanup via docker compose down and docker rm -f on the stale islandflow containers, but those commands hung for the delta user and timed out. Added repo guardrails and docs so deploy warns when the duplicate project exists, and opened islandflow-2db for manual host-level cleanup during a maintenance window.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:16:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:07Z","started_at":"2026-05-16T01:16:09Z","closed_at":"2026-05-16T01:28:07Z","close_reason":"Completed the repo-side investigation and guardrails. Actual server-side container removal is blocked by hanging Docker operations and is tracked separately in islandflow-2db for a maintenance window.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-4gj","title":"Clarify Docker-first deploy workflow and mark native runtime experimental","description":"After inspecting the live VPS, native deployment is not ready for routine use: Nginx Proxy Manager routes to Docker container names, Bun is not installed on the host, sudo systemctl is not passwordless, and no Islandflow units exist. Update deploy messaging and docs so Docker remains the clearly recommended deployment path and native runtime is labeled experimental/future-facing with server prerequisites called out.","notes":"Updated deploy messaging and docs after live VPS inspection. scripts/deploy.ts now marks Docker as the default and recommended runtime, labels native as experimental, switches native systemctl default to sudo -n systemctl, and prints explicit native precheck failures for missing Bun/systemctl access/units. Updated README.md, deployment/docker/README.md, and deployment/native/README.md to reflect the current Docker + Nginx Proxy Manager topology. Validation: ./deploy --help, ./deploy main --runtime native --no-build (fails fast with Bun-missing message), bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:10:11Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:12:39Z","started_at":"2026-05-16T01:10:14Z","closed_at":"2026-05-16T01:12:39Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-4gj","title":"Clarify Docker-first deploy workflow and mark native runtime experimental","description":"After inspecting the live VPS, native deployment is not ready for routine use: Nginx Proxy Manager routes to Docker container names, Bun is not installed on the host, sudo systemctl is not passwordless, and no Islandflow units exist. Update deploy messaging and docs so Docker remains the clearly recommended deployment path and native runtime is labeled experimental/future-facing with server prerequisites called out.","notes":"Updated deploy messaging and docs after live VPS inspection. scripts/deploy.ts now marks Docker as the default and recommended runtime, labels native as experimental, switches native systemctl default to sudo -n systemctl, and prints explicit native precheck failures for missing Bun/systemctl access/units. Updated README.md, deployment/docker/README.md, and deployment/native/README.md to reflect the current Docker + Nginx Proxy Manager topology. Validation: ./deploy --help, ./deploy main --runtime native --no-build (fails fast with Bun-missing message), bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:10:11Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:12:39Z","started_at":"2026-05-16T01:10:14Z","closed_at":"2026-05-16T01:12:39Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"islandflow-7p2","title":"Fix deploy wrapper argument forwarding for runtime flags","description":"The repo-root deploy wrapper currently invokes bun run without a -- separator, so flags like --runtime native are treated as Bun CLI flags instead of script arguments. Update the wrapper so ./deploy main --runtime native forwards arguments correctly to scripts/deploy.ts.","notes":"Cherry-picked the dual-runtime deploy workflow onto main and fixed the repo-root deploy wrapper to call Bun with a -- separator so flags like --runtime native are forwarded to scripts/deploy.ts correctly. Validation: ./deploy --help, ./deploy main --runtime native --force-recreate guard, bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T00:51:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T00:52:34Z","started_at":"2026-05-16T00:51:10Z","closed_at":"2026-05-16T00:52:34Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"islandflow-7p2","title":"Fix deploy wrapper argument forwarding for runtime flags","description":"The repo-root deploy wrapper currently invokes bun run without a -- separator, so flags like --runtime native are treated as Bun CLI flags instead of script arguments. Update the wrapper so ./deploy main --runtime native forwards arguments correctly to scripts/deploy.ts.","notes":"Cherry-picked the dual-runtime deploy workflow onto main and fixed the repo-root deploy wrapper to call Bun with a -- separator so flags like --runtime native are forwarded to scripts/deploy.ts correctly. Validation: ./deploy --help, ./deploy main --runtime native --force-recreate guard, bun run check:docker-workspace.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T00:51:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T00:52:34Z","started_at":"2026-05-16T00:51:10Z","closed_at":"2026-05-16T00:52:34Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|
@ -44,6 +53,8 @@
|
||||||
{"_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-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-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-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-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":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:46:42Z","created_by":"dirtydishes","updated_at":"2026-05-18T07:34:02Z","started_at":"2026-05-18T07:15:25Z","closed_at":"2026-05-18T07:34:02Z","close_reason":"Added checked-in native user unit templates, install/smoke-test/rollback helpers, updated native deploy docs with worker-first guidance, and installed the unit files onto the VPS in disabled form.","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}
|
{"_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}
|
||||||
|
|
|
||||||
174
AGENTS server.md
Normal file
174
AGENTS server.md
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
<!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:ca08a54f -->
|
||||||
|
## 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 <id> # View issue details
|
||||||
|
bd update <id> --claim # Claim work
|
||||||
|
bd close <id> # 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
|
||||||
|
<!-- END BEADS INTEGRATION -->
|
||||||
|
|
||||||
|
## 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/<name>`, `packages/<name>`, `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
|
||||||
15
AGENTS.md
15
AGENTS.md
|
|
@ -97,9 +97,11 @@ docs/turns/2026-05-14-add-market-replay-controls.html
|
||||||
|
|
||||||
### Format
|
### 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 concise summary at the top
|
||||||
- A detailed explanation of what changed
|
- A detailed explanation of what changed
|
||||||
|
|
@ -117,10 +119,11 @@ Each turn document must include these sections:
|
||||||
2. **Changes Made**
|
2. **Changes Made**
|
||||||
3. **Context**
|
3. **Context**
|
||||||
4. **Important Implementation Details**
|
4. **Important Implementation Details**
|
||||||
5. **Expected Impact for End-Users**
|
5. **Relevant Diff Snippets**
|
||||||
5. **Validation**
|
6. **Expected Impact for End-Users**
|
||||||
6. **Issues, Limitations, and Mitigations**
|
7. **Validation**
|
||||||
7. **Follow-up Work**
|
8. **Issues, Limitations, and Mitigations**
|
||||||
|
9. **Follow-up Work**
|
||||||
|
|
||||||
### Completion Rule
|
### Completion Rule
|
||||||
|
|
||||||
|
|
|
||||||
390
README.md
390
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.
|
> **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,
|
- options prints + NBBO,
|
||||||
- off-exchange equity prints,
|
- off-exchange equity prints,
|
||||||
- explainable rule-based flow classification,
|
- market news context,
|
||||||
|
- explainable smart-money flow classification,
|
||||||
- deterministic replay,
|
- deterministic replay,
|
||||||
- evidence-linked UI inspection.
|
- evidence-linked UI inspection.
|
||||||
|
|
||||||
|
|
@ -19,126 +20,176 @@ This repository contains a Bun + TypeScript monorepo for a personal-use, event-s
|
||||||
Implemented now:
|
Implemented now:
|
||||||
|
|
||||||
- Bun workspaces with shared packages for schemas, bus, config, observability, and ClickHouse access.
|
- Bun workspaces with shared packages for schemas, bus, config, observability, and ClickHouse access.
|
||||||
- Infra orchestration via Docker Compose (NATS JetStream, ClickHouse, Redis).
|
- Infra orchestration via Docker Compose for local NATS JetStream, ClickHouse, and Redis.
|
||||||
- Options ingest service with adapters:
|
- Options ingest service with synthetic, Alpaca options, IBKR bridge, and Databento historical replay adapters.
|
||||||
- synthetic stream,
|
- Equities ingest service with synthetic and Alpaca equities trades/quotes adapters.
|
||||||
- Alpaca options (dev-focused, bounded contracts),
|
- News ingest service for Alpaca news backfill and websocket publication.
|
||||||
- IBKR bridge (Python sidecar),
|
- 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.
|
||||||
- Databento historical replay adapter (Python sidecar).
|
- Candles service for server-side equity candle aggregation, ClickHouse persistence, optional Redis hot cache, and NATS publication.
|
||||||
- Equities ingest service with adapters:
|
- Replay service for deterministic ClickHouse-to-NATS republishing with multi-stream merge, stable tie-break ordering, speed, start, and end controls.
|
||||||
- synthetic stream,
|
- 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.
|
||||||
- Alpaca equities trades/quotes.
|
- Next.js web app upgraded to Next.js `16.2.6`, React `19.2.0`, and React DOM `19.2.0`.
|
||||||
- Compute service:
|
- Evidence-centric terminal UI, live/replay controls, chart-focused routes, news view, profile-aware smart-money display, and alert-context hydration.
|
||||||
- deterministic option print clustering into `FlowPacket`s,
|
- Thin Electron desktop shell in `apps/desktop` that can wrap the hosted app or local web UI.
|
||||||
- NBBO join quality features and aggressor-mix metrics,
|
- Refdata + EOD enricher service entrypoints are present, with refdata able to validate or refresh the event-calendar cache.
|
||||||
- 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).
|
|
||||||
|
|
||||||
Planned / not yet complete:
|
Planned / not yet complete:
|
||||||
|
|
||||||
- production-grade licensed feed integrations and entitlement workflow,
|
- production-grade licensed feed integrations and entitlement workflow,
|
||||||
- richer refdata/corp-action enrichment,
|
- richer refdata/corp-action enrichment,
|
||||||
- secure deployment/auth hardening,
|
- 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
|
## Core Principles
|
||||||
|
|
||||||
- **Explainability first** — inferred outputs are evidence-backed and human-readable.
|
- **Explainability first**: inferred outputs are evidence-backed and human-readable.
|
||||||
- **Event sourcing** — raw and derived events persist to support replay.
|
- **Event sourcing**: raw and derived events persist to support replay.
|
||||||
- **Determinism** — replay behavior tracks live pipeline logic.
|
- **Determinism**: replay behavior tracks live pipeline logic.
|
||||||
- **Microstructure awareness** — bounded joins, confidence scoring, and explicit uncertainty.
|
- **Microstructure awareness**: bounded joins, confidence scoring, and explicit uncertainty.
|
||||||
- **Bun-first tooling** — runtime/package/scripts all use Bun.
|
- **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
|
## Monorepo Layout
|
||||||
|
|
||||||
- `apps/web` — Next.js UI shell/routes.
|
- `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-options` — options print/NBBO ingest adapters.
|
||||||
- `services/ingest-equities` — equity print/quote 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/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/api` — REST + WebSocket gateway.
|
||||||
- `services/refdata` — scaffold service.
|
- `services/refdata` — event-calendar validation/provider refresh scaffolding.
|
||||||
- `services/eod-enricher` — scaffold service.
|
- `services/eod-enricher` — scaffold service.
|
||||||
- `packages/types` — shared event schemas/types.
|
- `packages/types` — shared event schemas/types.
|
||||||
- `packages/storage` — ClickHouse tables/queries.
|
- `packages/storage` — ClickHouse tables/queries.
|
||||||
- `packages/bus` — NATS/JetStream helpers.
|
- `packages/bus` — NATS/JetStream helpers.
|
||||||
- `packages/config` — env parsing.
|
- `packages/config` — env parsing.
|
||||||
- `packages/observability` — logger + metrics facade.
|
- `packages/observability` — logger + metrics facade.
|
||||||
|
- `deployment/docker` — supported VPS Docker Compose runtime.
|
||||||
|
- `deployment/native` — experimental host-native Bun + systemd deployment notes.
|
||||||
|
|
||||||
## Build and Run
|
## Build and Run
|
||||||
|
|
||||||
Install dependencies:
|
Install dependencies:
|
||||||
|
|
||||||
- `bun install`
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
Start infrastructure only:
|
Start infrastructure only:
|
||||||
|
|
||||||
- `docker compose up -d`
|
```bash
|
||||||
|
bun run dev:infra
|
||||||
|
```
|
||||||
|
|
||||||
Create env file:
|
Create env file:
|
||||||
|
|
||||||
- copy `.env.example` to `.env` and set provider credentials as needed.
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
Start infra + all services + web:
|
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:
|
Start web only:
|
||||||
|
|
||||||
- `bun run dev:web`
|
```bash
|
||||||
|
bun run dev:web
|
||||||
|
```
|
||||||
|
|
||||||
Recommended fast iteration loop:
|
Recommended fast iteration loop:
|
||||||
|
|
||||||
- `bun run dev:infra` for Docker-backed infra only
|
```bash
|
||||||
- `bun run dev:services` for native Bun backend services
|
bun run dev:infra
|
||||||
- `bun run dev:web` for the local Next.js UI
|
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
|
## Deployment Workflow
|
||||||
|
|
||||||
- `./deploy main` keeps the current VPS Docker rollout path as the default and recommended path.
|
Docker remains the supported and recommended path for the current VPS.
|
||||||
- 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.
|
```bash
|
||||||
- Native deploys are now intended primarily for worker-only fast iteration until the public edge is cut over deliberately.
|
./deploy main
|
||||||
- `./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.
|
./deploy main --runtime docker
|
||||||
- Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, `--workers-only`, and `--no-build`.
|
./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`, `--workers-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.
|
||||||
- When run from `/home/delta/islandflow` on the VPS itself, `./deploy` can execute locally instead of SSHing back into the same server.
|
- When run from `/home/delta/islandflow` on the VPS itself, `./deploy` can execute locally instead of SSHing back into the same server.
|
||||||
- Docker runtime details live in `deployment/docker/README.md`.
|
- Native deployment is opt-in and experimental:
|
||||||
- Native runtime expectations and prerequisites live in `deployment/native/README.md`.
|
|
||||||
|
```bash
|
||||||
|
./deploy main --runtime native
|
||||||
|
./deploy current-branch --runtime native
|
||||||
|
```
|
||||||
|
|
||||||
|
Native deployment expects Bun, systemd units, host-reachable infra, and deliberate reverse-proxy changes. Native deploys are intended primarily for worker-only fast iteration until the public edge is cut over deliberately.
|
||||||
|
|
||||||
|
Read more:
|
||||||
|
|
||||||
|
- `deployment/docker/README.md`
|
||||||
|
- `deployment/native/README.md`
|
||||||
|
|
||||||
## Desktop Shell
|
## 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:
|
What it is:
|
||||||
|
|
||||||
|
|
@ -146,37 +197,35 @@ What it is:
|
||||||
- a native app window plus packaging/distribution shell,
|
- a native app window plus packaging/distribution shell,
|
||||||
- a way to run the existing web UI inside Electron without local backend services.
|
- 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 bundled backend runtime,
|
||||||
- a packaged local Next.js frontend in v1,
|
- a packaged local Next.js frontend,
|
||||||
- a desktop feature layer with notifications, preferences, or auto-updates yet.
|
- a desktop feature layer with notifications, preferences, auto-updates, signing, or notarization.
|
||||||
|
|
||||||
Run the desktop shell against a local web UI:
|
Run the desktop shell against a local web UI:
|
||||||
|
|
||||||
- `bun run dev:desktop`
|
```bash
|
||||||
|
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`.
|
```
|
||||||
|
|
||||||
Run the desktop shell directly against the hosted app:
|
Run the desktop shell directly against the hosted app:
|
||||||
|
|
||||||
- `bun run dev:desktop:remote`
|
```bash
|
||||||
|
bun run dev:desktop:remote
|
||||||
|
```
|
||||||
|
|
||||||
Package the desktop shell:
|
Package the desktop shell:
|
||||||
|
|
||||||
- `bun run package:desktop`
|
```bash
|
||||||
- `bun run make:desktop`
|
bun run package:desktop
|
||||||
|
bun run make:desktop
|
||||||
|
```
|
||||||
|
|
||||||
Desktop-specific environment:
|
Desktop-specific environment:
|
||||||
|
|
||||||
- `ISLANDFLOW_DESKTOP_START_URL` is only used by the Electron shell and is restricted to trusted Islandflow app origins.
|
- `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.
|
- `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.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Environment Configuration
|
## Environment Configuration
|
||||||
|
|
||||||
|
|
@ -198,32 +247,27 @@ All runtime configuration comes from `.env`.
|
||||||
| `OPTIONS_INGEST_ADAPTER` | `synthetic` | Options ingest source: `synthetic`, `alpaca`, `ibkr`, or `databento`. |
|
| `OPTIONS_INGEST_ADAPTER` | `synthetic` | Options ingest source: `synthetic`, `alpaca`, `ibkr`, or `databento`. |
|
||||||
| `EQUITIES_INGEST_ADAPTER` | `synthetic` | Equities ingest source: `synthetic` or `alpaca`. |
|
| `EQUITIES_INGEST_ADAPTER` | `synthetic` | Equities ingest source: `synthetic` or `alpaca`. |
|
||||||
| `EMIT_INTERVAL_MS` | `1000` | Emit cadence for synthetic ingest adapters. |
|
| `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_MARKET_MODE` | `realistic` | Shared synthetic profile: `realistic`, `active`, or `firehose`. |
|
||||||
| `SYNTHETIC_OPTIONS_MODE` | empty | Options-only synthetic profile override; falls back to `SYNTHETIC_MARKET_MODE`. |
|
| `SYNTHETIC_OPTIONS_MODE` | empty | Options-only synthetic profile override. |
|
||||||
| `SYNTHETIC_EQUITIES_MODE` | empty | Equities-only synthetic profile override; falls back to `SYNTHETIC_MARKET_MODE`. |
|
| `SYNTHETIC_EQUITIES_MODE` | empty | Equities-only synthetic profile override. |
|
||||||
|
|
||||||
Synthetic profile intent:
|
### Alpaca and news configuration
|
||||||
- `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
|
|
||||||
|
|
||||||
| Variable | Default | What it controls |
|
| 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_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 for contract discovery/reference calls. |
|
| `ALPACA_REST_URL` | `https://data.alpaca.markets` | Alpaca REST base URL. |
|
||||||
| `ALPACA_WS_BASE_URL` | `wss://stream.data.alpaca.markets/v1beta1` (options), `wss://stream.data.alpaca.markets` (equities) | Alpaca websocket 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 for Alpaca options (`indicative` or `opra`). |
|
| `ALPACA_FEED` | `indicative` | Options feed tier: `indicative` or `opra`. |
|
||||||
| `ALPACA_UNDERLYINGS` | `SPY,NVDA,AAPL` | Comma-separated symbols targeted by Alpaca ingest. |
|
| `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_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_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_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_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_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). |
|
| `ALPACA_EQUITIES_FEED` | `iex` | Alpaca equities feed: `iex` or `sip`. |
|
||||||
|
| `ALPACA_NEWS_BACKFILL_LIMIT` | `100` | Alpaca news stories fetched on startup, capped at 200. |
|
||||||
For Alpaca adapters, configure `ALPACA_API_KEY`.
|
| `ALPACA_NEWS_WEBSOCKET_PATH` | `/v1beta1/news` | Alpaca news websocket path. |
|
||||||
|
|
||||||
### Databento replay adapter configuration
|
### Databento replay adapter configuration
|
||||||
|
|
||||||
|
|
@ -238,7 +282,7 @@ For Alpaca adapters, configure `ALPACA_API_KEY`.
|
||||||
| `DATABENTO_SYMBOLS` | `ALL` | Symbol selection forwarded to Databento sidecar query. |
|
| `DATABENTO_SYMBOLS` | `ALL` | Symbol selection forwarded to Databento sidecar query. |
|
||||||
| `DATABENTO_STYPE_IN` | `raw_symbol` | Databento input symbology type. |
|
| `DATABENTO_STYPE_IN` | `raw_symbol` | Databento input symbology type. |
|
||||||
| `DATABENTO_STYPE_OUT` | `raw_symbol` | Databento output 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_PRICE_SCALE` | `1` | Multiplier applied to decoded prices from sidecar output. |
|
||||||
| `DATABENTO_PYTHON_BIN` | `python3` | Python executable used to run Databento sidecar script. |
|
| `DATABENTO_PYTHON_BIN` | `python3` | Python executable used to run Databento sidecar script. |
|
||||||
|
|
||||||
|
|
@ -250,9 +294,9 @@ For Alpaca adapters, configure `ALPACA_API_KEY`.
|
||||||
| `IBKR_PORT` | `7497` | TWS/Gateway port for IBKR bridge. |
|
| `IBKR_PORT` | `7497` | TWS/Gateway port for IBKR bridge. |
|
||||||
| `IBKR_CLIENT_ID` | `0` | IBKR client id used by the bridge connection. |
|
| `IBKR_CLIENT_ID` | `0` | IBKR client id used by the bridge connection. |
|
||||||
| `IBKR_SYMBOL` | `SPY` | Underlying symbol requested from IBKR. |
|
| `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_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_EXCHANGE` | `SMART` | IBKR exchange routing code. |
|
||||||
| `IBKR_CURRENCY` | `USD` | Contract currency. |
|
| `IBKR_CURRENCY` | `USD` | Contract currency. |
|
||||||
| `IBKR_PYTHON_BIN` | `python3` | Python executable used for IBKR sidecar. |
|
| `IBKR_PYTHON_BIN` | `python3` | Python executable used for IBKR sidecar. |
|
||||||
|
|
@ -261,133 +305,77 @@ For Alpaca adapters, configure `ALPACA_API_KEY`.
|
||||||
|
|
||||||
| Variable | Default | What it controls |
|
| 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_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_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_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_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 |
|
| Variable | Default | What it controls |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `CLUSTER_WINDOW_MS` | `500` | Time window used to cluster nearby option prints into a packet candidate. |
|
| `CLUSTER_WINDOW_MS` | `500` | Time window used to cluster nearby option prints into packet candidates. |
|
||||||
| `COMPUTE_DELIVER_POLICY` | `new` | Consumer start policy for compute stream subscriptions (`new`, `all`, `last`, `last_per_subject`). |
|
| `COMPUTE_DELIVER_POLICY` | `new` | Consumer start policy for compute subscriptions. |
|
||||||
| `COMPUTE_CONSUMER_RESET` | `false` | If true, resets durable consumer position for compute on startup. |
|
| `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. |
|
| `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_WINDOW_SIZE` | `50` | Number of observations retained per rolling metric key. |
|
||||||
| `ROLLING_TTL_SEC` | `86400` | Redis TTL for rolling metric keys. |
|
| `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. |
|
| `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_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_COOLDOWN_MS` | `30000` | Cooldown before repeated dark inferences for same symbol/pattern. |
|
||||||
| `DARK_INFER_MIN_BLOCK_SIZE` | `2000` | Minimum single-print size for block-style dark inference evidence. |
|
| `SMART_MONEY_EVENT_CALENDAR_PATH` | empty | Optional JSON event-calendar file used by compute. |
|
||||||
| `DARK_INFER_MIN_ACCUM_SIZE` | `3000` | Minimum aggregate size for accumulation-style dark inference evidence. |
|
| `REFDATA_EVENT_CALENDAR_PATH` | empty | Optional JSON event-calendar path for refdata; falls back to `SMART_MONEY_EVENT_CALENDAR_PATH`. |
|
||||||
| `DARK_INFER_MIN_ACCUM_COUNT` | `4` | Minimum print count for accumulation-style dark inference. |
|
| `REFDATA_EVENT_CALENDAR_PROVIDER` | empty | Set to `alpha_vantage` to refresh event-calendar cache from Alpha Vantage. |
|
||||||
| `DARK_INFER_MIN_PRINT_SIZE` | `200` | Minimum print size considered as dark inference evidence. |
|
| `ALPHA_VANTAGE_API_KEY` | empty | Alpha Vantage key for provider-backed event-calendar refresh. |
|
||||||
| `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. |
|
|
||||||
|
|
||||||
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.
|
### API, live cache, and web client
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
| Variable | Default | What it controls |
|
| Variable | Default | What it controls |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `API_PORT` | `4000` | API service listen port. |
|
| `API_PORT` | `4000` | API service listen port. |
|
||||||
| `REST_DEFAULT_LIMIT` | `200` | Default record count when a REST endpoint omits `limit`. |
|
| `REST_DEFAULT_LIMIT` | `200` | Default REST record count. |
|
||||||
| `API_DELIVER_POLICY` | `new` | JetStream consumer start policy used by API live subscribers (`new`, `all`, `last`, `last_per_subject`). |
|
| `API_DELIVER_POLICY` | `new` | JetStream consumer start policy used by API live subscribers. |
|
||||||
| `API_CONSUMER_RESET` | `false` | If true, API resets/recreates its live durable consumers on startup. |
|
| `API_CONSUMER_RESET` | `false` | Resets/recreates API live durable consumers on startup when true. |
|
||||||
| `LIVE_LIMIT_OPTIONS` | `10000` | In-memory/Redis live cache depth for options channel (clamped `1..100000`). |
|
| `LIVE_LIMIT_DEFAULT` | `1000` | Optional generic live cache depth default. |
|
||||||
| `LIVE_LIMIT_NBBO` | `10000` | Live cache depth for options NBBO channel (clamped `1..100000`). |
|
| `LIVE_LIMIT_FLOW` | `500` | Live cache depth for flow packet events unless overridden. |
|
||||||
| `LIVE_LIMIT_EQUITIES` | `10000` | Live cache depth for equities channel (clamped `1..100000`). |
|
| `LIVE_LIMIT_SMART_MONEY` | `300` | Live cache depth for smart-money events unless overridden. |
|
||||||
| `LIVE_LIMIT_EQUITY_QUOTES` | `10000` | Live cache depth for equity quotes channel (clamped `1..100000`). |
|
| `LIVE_LIMIT_OPTIONS` | `1000` | Live cache depth for options channel unless overridden. |
|
||||||
| `LIVE_LIMIT_EQUITY_JOINS` | `10000` | Live cache depth for equity join channel (clamped `1..100000`). |
|
| `LIVE_LIMIT_ALERTS` | `300` | Live cache depth for alerts channel unless overridden. |
|
||||||
| `LIVE_LIMIT_FLOW` | `10000` | Live cache depth for flow packet channel (clamped `1..100000`). |
|
| `LIVE_LIMIT_NEWS` | `100` | Live cache depth for news channel unless overridden. |
|
||||||
| `LIVE_LIMIT_CLASSIFIER_HITS` | `10000` | Live cache depth for classifier hits channel (clamped `1..100000`). |
|
| `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. |
|
||||||
| `LIVE_LIMIT_ALERTS` | `10000` | Live cache depth for alerts channel (clamped `1..100000`). |
|
| `NEXT_PUBLIC_LIVE_HOT_WINDOW` | `600` | Max hot-window items retained for non-options live streams in UI state. |
|
||||||
| `LIVE_LIMIT_INFERRED_DARK` | `10000` | Live cache depth for inferred dark channel (clamped `1..100000`). |
|
| `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. |
|
||||||
### Web client configuration (`NEXT_PUBLIC_*`)
|
| `NEXT_PUBLIC_FLOW_FILTER_PRESET` | `smart-money` | Default flow filter preset: `smart-money`, `balanced`, or `all`. |
|
||||||
|
|
||||||
| 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`). |
|
|
||||||
|
|
||||||
### Replay and testing controls
|
### Replay and testing controls
|
||||||
|
|
||||||
| Variable | Default | What it controls |
|
| Variable | Default | What it controls |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `REPLAY_ENABLED` | `false` | Dev-script toggle: starts replay service in `bun run dev` when truthy. |
|
| `REPLAY_ENABLED` | `false` | 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_STREAMS` | `options,nbbo,equities,equity-quotes` | Replay stream selection. |
|
||||||
| `REPLAY_START_TS` | `0` | Replay lower-bound timestamp; `0` means from earliest stored data. |
|
| `REPLAY_START_TS` | `0` | Replay lower-bound timestamp. |
|
||||||
| `REPLAY_END_TS` | `0` | Replay upper-bound timestamp; `0` means no explicit end bound. |
|
| `REPLAY_END_TS` | `0` | Replay upper-bound timestamp. |
|
||||||
| `REPLAY_SPEED` | `1` | Replay speed multiplier relative to original event timing. |
|
| `REPLAY_SPEED` | `1` | Replay speed multiplier. |
|
||||||
| `REPLAY_BATCH_SIZE` | `200` | Batch fetch size per replay stream pull. |
|
| `REPLAY_BATCH_SIZE` | `200` | Batch fetch size per stream. |
|
||||||
| `REPLAY_LOG_EVERY` | `1000` | Progress log interval (emitted event count). |
|
| `REPLAY_LOG_EVERY` | `1000` | Progress log interval. |
|
||||||
| `TESTING_MODE` | `false` | Enables ingest publish throttling for deterministic/lower-volume test runs. |
|
| `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`. |
|
| `TESTING_THROTTLE_MS` | `200` | Minimum delay between emitted events while `TESTING_MODE=true`. |
|
||||||
|
|
||||||
## Quick Notes
|
## 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.
|
- 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:
|
- Option prints persist as enriched raw rows and can be queried as `view=signal` or `view=raw`.
|
||||||
- `view=signal` — default live/UI path and compute input.
|
- 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.
|
||||||
- `view=raw` — audit/debug path that preserves every stored print.
|
- Live retention uses ClickHouse for durable server history, Redis for bounded hot cache, and browser state for rendering windows/preferences.
|
||||||
- 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.
|
- Alert and drawer evidence is pinned and hydrated by id/trace so details remain inspectable after hot-window eviction.
|
||||||
- Live retention uses a two-tier model:
|
- 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.
|
||||||
- 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.
|
|
||||||
- This repository is for personal, non-redistributed usage.
|
- This repository is for personal, non-redistributed usage.
|
||||||
|
|
||||||
## Useful Examples
|
## Useful Examples
|
||||||
|
|
|
||||||
|
|
@ -708,7 +708,12 @@ h3 {
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
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(3),
|
||||||
|
.page-grid-home > :nth-child(4),
|
||||||
.page-grid-tape > :nth-child(1),
|
.page-grid-tape > :nth-child(1),
|
||||||
.page-grid-replay > :nth-child(1) {
|
.page-grid-replay > :nth-child(1) {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
|
|
@ -933,6 +938,7 @@ h3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-grid-home > :nth-child(3),
|
.page-grid-home > :nth-child(3),
|
||||||
|
.page-grid-home > :nth-child(4),
|
||||||
.page-grid-replay > :not(:first-child) {
|
.page-grid-replay > :not(:first-child) {
|
||||||
height: clamp(430px, 58vh, 760px);
|
height: clamp(430px, 58vh, 760px);
|
||||||
}
|
}
|
||||||
|
|
@ -1747,6 +1753,72 @@ h3 {
|
||||||
gap: 10px;
|
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-status-grid strong,
|
||||||
.synthetic-hit-row strong {
|
.synthetic-hit-row strong {
|
||||||
font-family: var(--font-mono), monospace;
|
font-family: var(--font-mono), monospace;
|
||||||
|
|
@ -1964,6 +2036,7 @@ h3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-grid-home > :nth-child(3),
|
.page-grid-home > :nth-child(3),
|
||||||
|
.page-grid-home > :nth-child(4),
|
||||||
.page-grid-tape > :nth-child(1),
|
.page-grid-tape > :nth-child(1),
|
||||||
.page-grid-replay > :nth-child(1) {
|
.page-grid-replay > :nth-child(1) {
|
||||||
grid-column: auto;
|
grid-column: auto;
|
||||||
|
|
@ -1973,6 +2046,7 @@ h3 {
|
||||||
.page-grid-home > :nth-child(1),
|
.page-grid-home > :nth-child(1),
|
||||||
.page-grid-home > :nth-child(2),
|
.page-grid-home > :nth-child(2),
|
||||||
.page-grid-home > :nth-child(3),
|
.page-grid-home > :nth-child(3),
|
||||||
|
.page-grid-home > :nth-child(4),
|
||||||
.page-grid-signals > .terminal-pane,
|
.page-grid-signals > .terminal-pane,
|
||||||
.page-grid-replay > :not(:first-child),
|
.page-grid-replay > :not(:first-child),
|
||||||
.page-grid-tape > :first-child,
|
.page-grid-tape > :first-child,
|
||||||
|
|
|
||||||
7
apps/web/app/news/page.tsx
Normal file
7
apps/web/app/news/page.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { NewsRoute } from "../terminal";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <NewsRoute />;
|
||||||
|
}
|
||||||
|
|
@ -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", () => {
|
it("scopes /charts subscriptions to chart channels only", () => {
|
||||||
const channels = getLiveManifest("/charts", "SPY", 60000, buildDefaultFlowFilters()).map(
|
const channels = getLiveManifest("/charts", "SPY", 60000, buildDefaultFlowFilters()).map(
|
||||||
(subscription) => subscription.channel
|
(subscription) => subscription.channel
|
||||||
|
|
@ -431,6 +440,13 @@ describe("route feature map", () => {
|
||||||
expect(features.equityOverlay).toBe(true);
|
expect(features.equityOverlay).toBe(true);
|
||||||
expect(features.alerts).toBe(false);
|
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", () => {
|
describe("fixed tape virtualization config", () => {
|
||||||
|
|
@ -461,10 +477,11 @@ describe("dark underlying route dependency helper", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("terminal navigation", () => {
|
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([
|
expect(NAV_ITEMS).toEqual([
|
||||||
{ href: "/", label: "Home" },
|
{ href: "/", label: "Home" },
|
||||||
{ href: "/tape", label: "Tape" }
|
{ href: "/tape", label: "Tape" },
|
||||||
|
{ href: "/news", label: "News" }
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import type {
|
||||||
LiveServerMessage,
|
LiveServerMessage,
|
||||||
LiveHotChannelHealthMap,
|
LiveHotChannelHealthMap,
|
||||||
LiveSubscription,
|
LiveSubscription,
|
||||||
|
NewsStory,
|
||||||
OptionFlowFilters,
|
OptionFlowFilters,
|
||||||
OptionFlowView,
|
OptionFlowView,
|
||||||
OptionNbboSide,
|
OptionNbboSide,
|
||||||
|
|
@ -158,6 +159,7 @@ type RouteFeatures = {
|
||||||
nbbo: boolean;
|
nbbo: boolean;
|
||||||
equities: boolean;
|
equities: boolean;
|
||||||
flow: boolean;
|
flow: boolean;
|
||||||
|
news: boolean;
|
||||||
alerts: boolean;
|
alerts: boolean;
|
||||||
smartMoney: boolean;
|
smartMoney: boolean;
|
||||||
classifierHits: boolean;
|
classifierHits: boolean;
|
||||||
|
|
@ -168,6 +170,7 @@ type RouteFeatures = {
|
||||||
showOptionsPane: boolean;
|
showOptionsPane: boolean;
|
||||||
showEquitiesPane: boolean;
|
showEquitiesPane: boolean;
|
||||||
showFlowPane: boolean;
|
showFlowPane: boolean;
|
||||||
|
showNewsPane: boolean;
|
||||||
showAlertsPane: boolean;
|
showAlertsPane: boolean;
|
||||||
showClassifierPane: boolean;
|
showClassifierPane: boolean;
|
||||||
showDarkPane: boolean;
|
showDarkPane: boolean;
|
||||||
|
|
@ -187,6 +190,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
const includeEquitiesFallback = shouldIncludeEquitiesForDarkUnderlyingFallback();
|
const includeEquitiesFallback = shouldIncludeEquitiesForDarkUnderlyingFallback();
|
||||||
const normalizedPath =
|
const normalizedPath =
|
||||||
pathname === "/tape" ||
|
pathname === "/tape" ||
|
||||||
|
pathname === "/news" ||
|
||||||
pathname === "/signals" ||
|
pathname === "/signals" ||
|
||||||
pathname === "/charts" ||
|
pathname === "/charts" ||
|
||||||
pathname === "/replay"
|
pathname === "/replay"
|
||||||
|
|
@ -200,6 +204,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
nbbo: true,
|
nbbo: true,
|
||||||
equities: true,
|
equities: true,
|
||||||
flow: true,
|
flow: true,
|
||||||
|
news: false,
|
||||||
alerts: false,
|
alerts: false,
|
||||||
smartMoney: false,
|
smartMoney: false,
|
||||||
classifierHits: false,
|
classifierHits: false,
|
||||||
|
|
@ -210,6 +215,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
showOptionsPane: true,
|
showOptionsPane: true,
|
||||||
showEquitiesPane: true,
|
showEquitiesPane: true,
|
||||||
showFlowPane: true,
|
showFlowPane: true,
|
||||||
|
showNewsPane: false,
|
||||||
showAlertsPane: false,
|
showAlertsPane: false,
|
||||||
showClassifierPane: false,
|
showClassifierPane: false,
|
||||||
showDarkPane: false,
|
showDarkPane: false,
|
||||||
|
|
@ -220,12 +226,41 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
needsAlertEvidencePrefetch: false,
|
needsAlertEvidencePrefetch: false,
|
||||||
needsDarkUnderlying: 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":
|
case "/signals":
|
||||||
return {
|
return {
|
||||||
options: false,
|
options: false,
|
||||||
nbbo: false,
|
nbbo: false,
|
||||||
equities: includeEquitiesFallback,
|
equities: includeEquitiesFallback,
|
||||||
flow: false,
|
flow: false,
|
||||||
|
news: false,
|
||||||
alerts: true,
|
alerts: true,
|
||||||
smartMoney: true,
|
smartMoney: true,
|
||||||
classifierHits: true,
|
classifierHits: true,
|
||||||
|
|
@ -236,6 +271,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
showOptionsPane: false,
|
showOptionsPane: false,
|
||||||
showEquitiesPane: false,
|
showEquitiesPane: false,
|
||||||
showFlowPane: false,
|
showFlowPane: false,
|
||||||
|
showNewsPane: false,
|
||||||
showAlertsPane: true,
|
showAlertsPane: true,
|
||||||
showClassifierPane: true,
|
showClassifierPane: true,
|
||||||
showDarkPane: true,
|
showDarkPane: true,
|
||||||
|
|
@ -252,6 +288,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
nbbo: false,
|
nbbo: false,
|
||||||
equities: includeEquitiesFallback,
|
equities: includeEquitiesFallback,
|
||||||
flow: false,
|
flow: false,
|
||||||
|
news: false,
|
||||||
alerts: false,
|
alerts: false,
|
||||||
smartMoney: true,
|
smartMoney: true,
|
||||||
classifierHits: false,
|
classifierHits: false,
|
||||||
|
|
@ -262,6 +299,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
showOptionsPane: false,
|
showOptionsPane: false,
|
||||||
showEquitiesPane: false,
|
showEquitiesPane: false,
|
||||||
showFlowPane: false,
|
showFlowPane: false,
|
||||||
|
showNewsPane: false,
|
||||||
showAlertsPane: false,
|
showAlertsPane: false,
|
||||||
showClassifierPane: false,
|
showClassifierPane: false,
|
||||||
showDarkPane: false,
|
showDarkPane: false,
|
||||||
|
|
@ -278,6 +316,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
nbbo: false,
|
nbbo: false,
|
||||||
equities: false,
|
equities: false,
|
||||||
flow: false,
|
flow: false,
|
||||||
|
news: false,
|
||||||
alerts: false,
|
alerts: false,
|
||||||
smartMoney: false,
|
smartMoney: false,
|
||||||
classifierHits: false,
|
classifierHits: false,
|
||||||
|
|
@ -288,6 +327,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
showOptionsPane: true,
|
showOptionsPane: true,
|
||||||
showEquitiesPane: false,
|
showEquitiesPane: false,
|
||||||
showFlowPane: true,
|
showFlowPane: true,
|
||||||
|
showNewsPane: false,
|
||||||
showAlertsPane: true,
|
showAlertsPane: true,
|
||||||
showClassifierPane: false,
|
showClassifierPane: false,
|
||||||
showDarkPane: false,
|
showDarkPane: false,
|
||||||
|
|
@ -305,6 +345,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
nbbo: false,
|
nbbo: false,
|
||||||
equities: true,
|
equities: true,
|
||||||
flow: false,
|
flow: false,
|
||||||
|
news: true,
|
||||||
alerts: true,
|
alerts: true,
|
||||||
smartMoney: true,
|
smartMoney: true,
|
||||||
classifierHits: false,
|
classifierHits: false,
|
||||||
|
|
@ -315,6 +356,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
|
||||||
showOptionsPane: false,
|
showOptionsPane: false,
|
||||||
showEquitiesPane: true,
|
showEquitiesPane: true,
|
||||||
showFlowPane: false,
|
showFlowPane: false,
|
||||||
|
showNewsPane: true,
|
||||||
showAlertsPane: true,
|
showAlertsPane: true,
|
||||||
showClassifierPane: false,
|
showClassifierPane: false,
|
||||||
showDarkPane: false,
|
showDarkPane: false,
|
||||||
|
|
@ -332,6 +374,7 @@ const EMPTY_ALERT_EVENTS: AlertEvent[] = [];
|
||||||
const EMPTY_CLASSIFIER_HIT_EVENTS: ClassifierHitEvent[] = [];
|
const EMPTY_CLASSIFIER_HIT_EVENTS: ClassifierHitEvent[] = [];
|
||||||
const EMPTY_SMART_MONEY_EVENTS: SmartMoneyEvent[] = [];
|
const EMPTY_SMART_MONEY_EVENTS: SmartMoneyEvent[] = [];
|
||||||
const EMPTY_INFERRED_DARK_EVENTS: InferredDarkEvent[] = [];
|
const EMPTY_INFERRED_DARK_EVENTS: InferredDarkEvent[] = [];
|
||||||
|
const EMPTY_NEWS_STORIES: NewsStory[] = [];
|
||||||
|
|
||||||
type CandlestickSeries = ReturnType<IChartApi["addCandlestickSeries"]>;
|
type CandlestickSeries = ReturnType<IChartApi["addCandlestickSeries"]>;
|
||||||
|
|
||||||
|
|
@ -1194,6 +1237,44 @@ const formatDateTime = (ts: number): string => {
|
||||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
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(/<script[\s\S]*?<\/script>/gi, " ")
|
||||||
|
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
||||||
|
.replace(/<[^>]+>/g, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sanitized = value
|
||||||
|
.replace(/<script[\s\S]*?<\/script>/gi, "")
|
||||||
|
.replace(/<style[\s\S]*?<\/style>/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 => {
|
const humanizeClassifierId = (value: string): string => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return "Classifier";
|
return "Classifier";
|
||||||
|
|
@ -1668,7 +1749,7 @@ export const getOptionTableSnapshot = (
|
||||||
};
|
};
|
||||||
|
|
||||||
type ListScrollState = {
|
type ListScrollState = {
|
||||||
listRef: React.RefObject<HTMLDivElement>;
|
listRef: React.RefObject<HTMLDivElement | null>;
|
||||||
listNode: HTMLDivElement | null;
|
listNode: HTMLDivElement | null;
|
||||||
setListRef: (node: HTMLDivElement | null) => void;
|
setListRef: (node: HTMLDivElement | null) => void;
|
||||||
isAtTop: boolean;
|
isAtTop: boolean;
|
||||||
|
|
@ -1773,7 +1854,7 @@ const useListScroll = (): ListScrollState => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const useScrollAnchor = (
|
const useScrollAnchor = (
|
||||||
listRef: React.RefObject<HTMLDivElement>,
|
listRef: React.RefObject<HTMLDivElement | null>,
|
||||||
isAtTopRef: React.MutableRefObject<boolean>
|
isAtTopRef: React.MutableRefObject<boolean>
|
||||||
) => {
|
) => {
|
||||||
const pendingRef = useRef<{
|
const pendingRef = useRef<{
|
||||||
|
|
@ -1915,7 +1996,7 @@ type TapeVirtualRow<T> = {
|
||||||
|
|
||||||
const useTapeVirtualList = <T extends SortableItem>(
|
const useTapeVirtualList = <T extends SortableItem>(
|
||||||
items: T[],
|
items: T[],
|
||||||
listRef: React.RefObject<HTMLDivElement>,
|
listRef: React.RefObject<HTMLDivElement | null>,
|
||||||
config: TapeVirtualListConfig
|
config: TapeVirtualListConfig
|
||||||
): TapeVirtualListResult<T> => {
|
): TapeVirtualListResult<T> => {
|
||||||
const virtualizer = useVirtualizer<HTMLDivElement, HTMLElement>({
|
const virtualizer = useVirtualizer<HTMLDivElement, HTMLElement>({
|
||||||
|
|
@ -2870,6 +2951,7 @@ type LiveSessionState = {
|
||||||
smartMoneyHistory: SmartMoneyEvent[];
|
smartMoneyHistory: SmartMoneyEvent[];
|
||||||
classifierHitsHistory: ClassifierHitEvent[];
|
classifierHitsHistory: ClassifierHitEvent[];
|
||||||
alertsHistory: AlertEvent[];
|
alertsHistory: AlertEvent[];
|
||||||
|
newsHistory: NewsStory[];
|
||||||
inferredDarkHistory: InferredDarkEvent[];
|
inferredDarkHistory: InferredDarkEvent[];
|
||||||
options: OptionPrint[];
|
options: OptionPrint[];
|
||||||
nbbo: OptionNBBO[];
|
nbbo: OptionNBBO[];
|
||||||
|
|
@ -2880,6 +2962,7 @@ type LiveSessionState = {
|
||||||
smartMoney: SmartMoneyEvent[];
|
smartMoney: SmartMoneyEvent[];
|
||||||
classifierHits: ClassifierHitEvent[];
|
classifierHits: ClassifierHitEvent[];
|
||||||
alerts: AlertEvent[];
|
alerts: AlertEvent[];
|
||||||
|
news: NewsStory[];
|
||||||
inferredDark: InferredDarkEvent[];
|
inferredDark: InferredDarkEvent[];
|
||||||
chartCandles: EquityCandle[];
|
chartCandles: EquityCandle[];
|
||||||
chartOverlay: EquityPrint[];
|
chartOverlay: EquityPrint[];
|
||||||
|
|
@ -2900,6 +2983,7 @@ const LIVE_HISTORY_ENDPOINTS: Partial<Record<LiveSubscription["channel"], string
|
||||||
"smart-money": "/history/smart-money",
|
"smart-money": "/history/smart-money",
|
||||||
"classifier-hits": "/history/classifier-hits",
|
"classifier-hits": "/history/classifier-hits",
|
||||||
alerts: "/history/alerts",
|
alerts: "/history/alerts",
|
||||||
|
news: "/history/news",
|
||||||
"inferred-dark": "/history/inferred-dark"
|
"inferred-dark": "/history/inferred-dark"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -3072,6 +3156,9 @@ export const getLiveManifest = (
|
||||||
if (features.flow) {
|
if (features.flow) {
|
||||||
subscriptions.push({ channel: "flow", filters: flowFilters, snapshot_limit: LIVE_HOT_WINDOW });
|
subscriptions.push({ channel: "flow", filters: flowFilters, snapshot_limit: LIVE_HOT_WINDOW });
|
||||||
}
|
}
|
||||||
|
if (features.news) {
|
||||||
|
subscriptions.push({ channel: "news", snapshot_limit: LIVE_OPTIONS_HEAD_LIMIT });
|
||||||
|
}
|
||||||
if (features.alerts) {
|
if (features.alerts) {
|
||||||
subscriptions.push({ channel: "alerts", snapshot_limit: LIVE_HOT_WINDOW });
|
subscriptions.push({ channel: "alerts", snapshot_limit: LIVE_HOT_WINDOW });
|
||||||
}
|
}
|
||||||
|
|
@ -3133,6 +3220,7 @@ const useLiveSession = (
|
||||||
const [smartMoney, setSmartMoney] = useState<SmartMoneyEvent[]>([]);
|
const [smartMoney, setSmartMoney] = useState<SmartMoneyEvent[]>([]);
|
||||||
const [classifierHits, setClassifierHits] = useState<ClassifierHitEvent[]>([]);
|
const [classifierHits, setClassifierHits] = useState<ClassifierHitEvent[]>([]);
|
||||||
const [alerts, setAlerts] = useState<AlertEvent[]>([]);
|
const [alerts, setAlerts] = useState<AlertEvent[]>([]);
|
||||||
|
const [news, setNews] = useState<NewsStory[]>([]);
|
||||||
const [inferredDark, setInferredDark] = useState<InferredDarkEvent[]>([]);
|
const [inferredDark, setInferredDark] = useState<InferredDarkEvent[]>([]);
|
||||||
const [optionsHistory, setOptionsHistory] = useState<OptionPrint[]>([]);
|
const [optionsHistory, setOptionsHistory] = useState<OptionPrint[]>([]);
|
||||||
const [nbboHistory, setNbboHistory] = useState<OptionNBBO[]>([]);
|
const [nbboHistory, setNbboHistory] = useState<OptionNBBO[]>([]);
|
||||||
|
|
@ -3142,6 +3230,7 @@ const useLiveSession = (
|
||||||
const [smartMoneyHistory, setSmartMoneyHistory] = useState<SmartMoneyEvent[]>([]);
|
const [smartMoneyHistory, setSmartMoneyHistory] = useState<SmartMoneyEvent[]>([]);
|
||||||
const [classifierHitsHistory, setClassifierHitsHistory] = useState<ClassifierHitEvent[]>([]);
|
const [classifierHitsHistory, setClassifierHitsHistory] = useState<ClassifierHitEvent[]>([]);
|
||||||
const [alertsHistory, setAlertsHistory] = useState<AlertEvent[]>([]);
|
const [alertsHistory, setAlertsHistory] = useState<AlertEvent[]>([]);
|
||||||
|
const [newsHistory, setNewsHistory] = useState<NewsStory[]>([]);
|
||||||
const [inferredDarkHistory, setInferredDarkHistory] = useState<InferredDarkEvent[]>([]);
|
const [inferredDarkHistory, setInferredDarkHistory] = useState<InferredDarkEvent[]>([]);
|
||||||
const [chartCandles, setChartCandles] = useState<EquityCandle[]>([]);
|
const [chartCandles, setChartCandles] = useState<EquityCandle[]>([]);
|
||||||
const [chartOverlay, setChartOverlay] = useState<EquityPrint[]>([]);
|
const [chartOverlay, setChartOverlay] = useState<EquityPrint[]>([]);
|
||||||
|
|
@ -3154,6 +3243,7 @@ const useLiveSession = (
|
||||||
const smartMoneyRef = useRef<SmartMoneyEvent[]>([]);
|
const smartMoneyRef = useRef<SmartMoneyEvent[]>([]);
|
||||||
const classifierHitsRef = useRef<ClassifierHitEvent[]>([]);
|
const classifierHitsRef = useRef<ClassifierHitEvent[]>([]);
|
||||||
const alertsRef = useRef<AlertEvent[]>([]);
|
const alertsRef = useRef<AlertEvent[]>([]);
|
||||||
|
const newsRef = useRef<NewsStory[]>([]);
|
||||||
const inferredDarkRef = useRef<InferredDarkEvent[]>([]);
|
const inferredDarkRef = useRef<InferredDarkEvent[]>([]);
|
||||||
const chartCandlesRef = useRef<EquityCandle[]>([]);
|
const chartCandlesRef = useRef<EquityCandle[]>([]);
|
||||||
const chartOverlayRef = useRef<EquityPrint[]>([]);
|
const chartOverlayRef = useRef<EquityPrint[]>([]);
|
||||||
|
|
@ -3165,6 +3255,7 @@ const useLiveSession = (
|
||||||
const smartMoneyHistoryRef = useRef<SmartMoneyEvent[]>([]);
|
const smartMoneyHistoryRef = useRef<SmartMoneyEvent[]>([]);
|
||||||
const classifierHitsHistoryRef = useRef<ClassifierHitEvent[]>([]);
|
const classifierHitsHistoryRef = useRef<ClassifierHitEvent[]>([]);
|
||||||
const alertsHistoryRef = useRef<AlertEvent[]>([]);
|
const alertsHistoryRef = useRef<AlertEvent[]>([]);
|
||||||
|
const newsHistoryRef = useRef<NewsStory[]>([]);
|
||||||
const inferredDarkHistoryRef = useRef<InferredDarkEvent[]>([]);
|
const inferredDarkHistoryRef = useRef<InferredDarkEvent[]>([]);
|
||||||
const socketRef = useRef<WebSocket | null>(null);
|
const socketRef = useRef<WebSocket | null>(null);
|
||||||
const reconnectRef = useRef<number | null>(null);
|
const reconnectRef = useRef<number | null>(null);
|
||||||
|
|
@ -3218,6 +3309,7 @@ const useLiveSession = (
|
||||||
setSmartMoney([]);
|
setSmartMoney([]);
|
||||||
setClassifierHits([]);
|
setClassifierHits([]);
|
||||||
setAlerts([]);
|
setAlerts([]);
|
||||||
|
setNews([]);
|
||||||
setInferredDark([]);
|
setInferredDark([]);
|
||||||
setOptionsHistory([]);
|
setOptionsHistory([]);
|
||||||
setNbboHistory([]);
|
setNbboHistory([]);
|
||||||
|
|
@ -3227,6 +3319,7 @@ const useLiveSession = (
|
||||||
setSmartMoneyHistory([]);
|
setSmartMoneyHistory([]);
|
||||||
setClassifierHitsHistory([]);
|
setClassifierHitsHistory([]);
|
||||||
setAlertsHistory([]);
|
setAlertsHistory([]);
|
||||||
|
setNewsHistory([]);
|
||||||
setInferredDarkHistory([]);
|
setInferredDarkHistory([]);
|
||||||
setChartCandles([]);
|
setChartCandles([]);
|
||||||
setChartOverlay([]);
|
setChartOverlay([]);
|
||||||
|
|
@ -3239,6 +3332,7 @@ const useLiveSession = (
|
||||||
smartMoneyRef.current = [];
|
smartMoneyRef.current = [];
|
||||||
classifierHitsRef.current = [];
|
classifierHitsRef.current = [];
|
||||||
alertsRef.current = [];
|
alertsRef.current = [];
|
||||||
|
newsRef.current = [];
|
||||||
inferredDarkRef.current = [];
|
inferredDarkRef.current = [];
|
||||||
chartCandlesRef.current = [];
|
chartCandlesRef.current = [];
|
||||||
chartOverlayRef.current = [];
|
chartOverlayRef.current = [];
|
||||||
|
|
@ -3250,6 +3344,7 @@ const useLiveSession = (
|
||||||
smartMoneyHistoryRef.current = [];
|
smartMoneyHistoryRef.current = [];
|
||||||
classifierHitsHistoryRef.current = [];
|
classifierHitsHistoryRef.current = [];
|
||||||
alertsHistoryRef.current = [];
|
alertsHistoryRef.current = [];
|
||||||
|
newsHistoryRef.current = [];
|
||||||
inferredDarkHistoryRef.current = [];
|
inferredDarkHistoryRef.current = [];
|
||||||
subscribedKeysRef.current = new Set();
|
subscribedKeysRef.current = new Set();
|
||||||
subscribedMapRef.current = new Map();
|
subscribedMapRef.current = new Map();
|
||||||
|
|
@ -3403,6 +3498,12 @@ const useLiveSession = (
|
||||||
ref: alertsHistoryRef
|
ref: alertsHistoryRef
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "news":
|
||||||
|
mergeItems(setNews, newsRef, items as NewsStory[], LIVE_OPTIONS_HEAD_LIMIT, {
|
||||||
|
setter: setNewsHistory,
|
||||||
|
ref: newsHistoryRef
|
||||||
|
});
|
||||||
|
break;
|
||||||
case "inferred-dark":
|
case "inferred-dark":
|
||||||
mergeItems(setInferredDark, inferredDarkRef, items as InferredDarkEvent[], LIVE_HOT_WINDOW, {
|
mergeItems(setInferredDark, inferredDarkRef, items as InferredDarkEvent[], LIVE_HOT_WINDOW, {
|
||||||
setter: setInferredDarkHistory,
|
setter: setInferredDarkHistory,
|
||||||
|
|
@ -3694,6 +3795,9 @@ const useLiveSession = (
|
||||||
case "alerts":
|
case "alerts":
|
||||||
mergeOlder(setAlertsHistory, alertsHistoryRef, alertsRef.current);
|
mergeOlder(setAlertsHistory, alertsHistoryRef, alertsRef.current);
|
||||||
break;
|
break;
|
||||||
|
case "news":
|
||||||
|
mergeOlder(setNewsHistory, newsHistoryRef, newsRef.current);
|
||||||
|
break;
|
||||||
case "inferred-dark":
|
case "inferred-dark":
|
||||||
mergeOlder(setInferredDarkHistory, inferredDarkHistoryRef, inferredDarkRef.current);
|
mergeOlder(setInferredDarkHistory, inferredDarkHistoryRef, inferredDarkRef.current);
|
||||||
break;
|
break;
|
||||||
|
|
@ -3735,6 +3839,7 @@ const useLiveSession = (
|
||||||
smartMoneyHistory,
|
smartMoneyHistory,
|
||||||
classifierHitsHistory,
|
classifierHitsHistory,
|
||||||
alertsHistory,
|
alertsHistory,
|
||||||
|
newsHistory,
|
||||||
inferredDarkHistory,
|
inferredDarkHistory,
|
||||||
options,
|
options,
|
||||||
nbbo,
|
nbbo,
|
||||||
|
|
@ -3745,6 +3850,7 @@ const useLiveSession = (
|
||||||
smartMoney,
|
smartMoney,
|
||||||
classifierHits,
|
classifierHits,
|
||||||
alerts,
|
alerts,
|
||||||
|
news,
|
||||||
inferredDark,
|
inferredDark,
|
||||||
chartCandles,
|
chartCandles,
|
||||||
chartOverlay
|
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 (
|
||||||
|
<aside className="drawer">
|
||||||
|
<div className="drawer-header">
|
||||||
|
<div>
|
||||||
|
<p className="drawer-eyebrow">News wire</p>
|
||||||
|
<h3>{story.headline}</h3>
|
||||||
|
<p className="drawer-subtitle">
|
||||||
|
{story.source} · Published {formatDateTime(story.published_ts)}
|
||||||
|
{story.updated_ts !== story.published_ts ? ` · Updated ${formatDateTime(story.updated_ts)}` : ""}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button className="drawer-close" type="button" onClick={onClose}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="drawer-meta">
|
||||||
|
{story.resolved_symbols.map((symbol) => (
|
||||||
|
<span className="drawer-chip" key={`${story.trace_id}-${symbol}`}>
|
||||||
|
{symbol}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
<span className="drawer-chip">{story.symbol_resolution}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{story.summary ? (
|
||||||
|
<div className="drawer-section">
|
||||||
|
<h4>Summary</h4>
|
||||||
|
<p className="drawer-note">{story.summary}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="drawer-section">
|
||||||
|
<h4>Story</h4>
|
||||||
|
{body.sanitized && body.html ? (
|
||||||
|
<div className="drawer-note news-drawer-body" dangerouslySetInnerHTML={{ __html: body.html }} />
|
||||||
|
) : body.fallbackText ? (
|
||||||
|
<p className="drawer-note">{body.fallbackText}</p>
|
||||||
|
) : (
|
||||||
|
<p className="drawer-empty">Story body unavailable.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{story.url ? (
|
||||||
|
<div className="drawer-section">
|
||||||
|
<h4>Source link</h4>
|
||||||
|
<a className="terminal-button terminal-link-button" href={story.url} rel="noreferrer" target="_blank">
|
||||||
|
Open original article
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
type ClassifierHitDrawerProps = {
|
type ClassifierHitDrawerProps = {
|
||||||
hit: ClassifierHitEvent;
|
hit: ClassifierHitEvent;
|
||||||
flowPacket: FlowPacket | null;
|
flowPacket: FlowPacket | null;
|
||||||
|
|
@ -5178,6 +5347,7 @@ const useTerminalState = () => {
|
||||||
const [mode, setMode] = useState<TapeMode>("live");
|
const [mode, setMode] = useState<TapeMode>("live");
|
||||||
const [replaySource, setReplaySource] = useState<string | null>(null);
|
const [replaySource, setReplaySource] = useState<string | null>(null);
|
||||||
const [selectedAlert, setSelectedAlert] = useState<AlertEvent | null>(null);
|
const [selectedAlert, setSelectedAlert] = useState<AlertEvent | null>(null);
|
||||||
|
const [selectedNewsStory, setSelectedNewsStory] = useState<NewsStory | null>(null);
|
||||||
const [selectedDarkEvent, setSelectedDarkEvent] = useState<InferredDarkEvent | null>(null);
|
const [selectedDarkEvent, setSelectedDarkEvent] = useState<InferredDarkEvent | null>(null);
|
||||||
const [selectedClassifierHit, setSelectedClassifierHit] = useState<ClassifierHitEvent | null>(null);
|
const [selectedClassifierHit, setSelectedClassifierHit] = useState<ClassifierHitEvent | null>(null);
|
||||||
const [selectedSmartMoneyEvent, setSelectedSmartMoneyEvent] = useState<SmartMoneyEvent | null>(null);
|
const [selectedSmartMoneyEvent, setSelectedSmartMoneyEvent] = useState<SmartMoneyEvent | null>(null);
|
||||||
|
|
@ -5274,12 +5444,13 @@ const useTerminalState = () => {
|
||||||
}, [mode]);
|
}, [mode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedAlert && !selectedClassifierHit && !selectedDarkEvent && !selectedSmartMoneyEvent) {
|
if (!selectedAlert && !selectedNewsStory && !selectedClassifierHit && !selectedDarkEvent && !selectedSmartMoneyEvent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dismissDrawers = () => {
|
const dismissDrawers = () => {
|
||||||
setSelectedAlert(null);
|
setSelectedAlert(null);
|
||||||
|
setSelectedNewsStory(null);
|
||||||
setSelectedClassifierHit(null);
|
setSelectedClassifierHit(null);
|
||||||
setSelectedSmartMoneyEvent(null);
|
setSelectedSmartMoneyEvent(null);
|
||||||
setSelectedDarkEvent(null);
|
setSelectedDarkEvent(null);
|
||||||
|
|
@ -5305,7 +5476,7 @@ const useTerminalState = () => {
|
||||||
document.removeEventListener("mousedown", handlePointerDown);
|
document.removeEventListener("mousedown", handlePointerDown);
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
document.removeEventListener("keydown", handleKeyDown);
|
||||||
};
|
};
|
||||||
}, [selectedAlert, selectedClassifierHit, selectedDarkEvent, selectedSmartMoneyEvent]);
|
}, [selectedAlert, selectedNewsStory, selectedClassifierHit, selectedDarkEvent, selectedSmartMoneyEvent]);
|
||||||
|
|
||||||
const optionsScroll = useListScroll();
|
const optionsScroll = useListScroll();
|
||||||
const equitiesScroll = useListScroll();
|
const equitiesScroll = useListScroll();
|
||||||
|
|
@ -5540,6 +5711,14 @@ const useTerminalState = () => {
|
||||||
)
|
)
|
||||||
: equityJoins;
|
: equityJoins;
|
||||||
const flowFeed = mode === "live" ? liveFlow : flow;
|
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 =
|
const alertsFeed =
|
||||||
mode === "live"
|
mode === "live"
|
||||||
? toStaticTapeState(
|
? toStaticTapeState(
|
||||||
|
|
@ -6490,6 +6669,16 @@ const useTerminalState = () => {
|
||||||
routeFeatures.needsAlertEvidencePrefetch
|
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(() => {
|
const visibleAlerts = useMemo(() => {
|
||||||
if (routeFeatures.needsAlertEvidencePrefetch) {
|
if (routeFeatures.needsAlertEvidencePrefetch) {
|
||||||
return filteredAlerts.slice(0, 12);
|
return filteredAlerts.slice(0, 12);
|
||||||
|
|
@ -6767,6 +6956,7 @@ const useTerminalState = () => {
|
||||||
(hit: ClassifierHitEvent) => {
|
(hit: ClassifierHitEvent) => {
|
||||||
const alert = findAlertForClassifierHit(hit);
|
const alert = findAlertForClassifierHit(hit);
|
||||||
if (alert) {
|
if (alert) {
|
||||||
|
setSelectedNewsStory(null);
|
||||||
setSelectedClassifierHit(null);
|
setSelectedClassifierHit(null);
|
||||||
setSelectedDarkEvent(null);
|
setSelectedDarkEvent(null);
|
||||||
setSelectedSmartMoneyEvent(null);
|
setSelectedSmartMoneyEvent(null);
|
||||||
|
|
@ -6774,6 +6964,7 @@ const useTerminalState = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSelectedNewsStory(null);
|
||||||
setSelectedAlert(null);
|
setSelectedAlert(null);
|
||||||
setSelectedDarkEvent(null);
|
setSelectedDarkEvent(null);
|
||||||
setSelectedSmartMoneyEvent(null);
|
setSelectedSmartMoneyEvent(null);
|
||||||
|
|
@ -6783,6 +6974,7 @@ const useTerminalState = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const openFromSmartMoneyEvent = useCallback((event: SmartMoneyEvent) => {
|
const openFromSmartMoneyEvent = useCallback((event: SmartMoneyEvent) => {
|
||||||
|
setSelectedNewsStory(null);
|
||||||
setSelectedAlert(null);
|
setSelectedAlert(null);
|
||||||
setSelectedClassifierHit(null);
|
setSelectedClassifierHit(null);
|
||||||
setSelectedDarkEvent(null);
|
setSelectedDarkEvent(null);
|
||||||
|
|
@ -6797,6 +6989,7 @@ const useTerminalState = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDarkMarkerClick = useCallback((event: InferredDarkEvent) => {
|
const handleDarkMarkerClick = useCallback((event: InferredDarkEvent) => {
|
||||||
|
setSelectedNewsStory(null);
|
||||||
setSelectedAlert(null);
|
setSelectedAlert(null);
|
||||||
setSelectedClassifierHit(null);
|
setSelectedClassifierHit(null);
|
||||||
setSelectedSmartMoneyEvent(null);
|
setSelectedSmartMoneyEvent(null);
|
||||||
|
|
@ -6817,6 +7010,9 @@ const useTerminalState = () => {
|
||||||
if (routeFeatures.flow || routeFeatures.showFlowPane) {
|
if (routeFeatures.flow || routeFeatures.showFlowPane) {
|
||||||
updates.push(flowFeed.lastUpdate);
|
updates.push(flowFeed.lastUpdate);
|
||||||
}
|
}
|
||||||
|
if (routeFeatures.news || routeFeatures.showNewsPane) {
|
||||||
|
updates.push(newsFeed.lastUpdate);
|
||||||
|
}
|
||||||
if (routeFeatures.alerts || routeFeatures.showAlertsPane) {
|
if (routeFeatures.alerts || routeFeatures.showAlertsPane) {
|
||||||
updates.push(alertsFeed.lastUpdate);
|
updates.push(alertsFeed.lastUpdate);
|
||||||
}
|
}
|
||||||
|
|
@ -6839,6 +7035,8 @@ const useTerminalState = () => {
|
||||||
routeFeatures.showFocusPane,
|
routeFeatures.showFocusPane,
|
||||||
routeFeatures.flow,
|
routeFeatures.flow,
|
||||||
routeFeatures.showFlowPane,
|
routeFeatures.showFlowPane,
|
||||||
|
routeFeatures.news,
|
||||||
|
routeFeatures.showNewsPane,
|
||||||
routeFeatures.alerts,
|
routeFeatures.alerts,
|
||||||
routeFeatures.showAlertsPane,
|
routeFeatures.showAlertsPane,
|
||||||
routeFeatures.smartMoney,
|
routeFeatures.smartMoney,
|
||||||
|
|
@ -6849,6 +7047,7 @@ const useTerminalState = () => {
|
||||||
equitiesFeed.lastUpdate,
|
equitiesFeed.lastUpdate,
|
||||||
inferredDarkFeed.lastUpdate,
|
inferredDarkFeed.lastUpdate,
|
||||||
flowFeed.lastUpdate,
|
flowFeed.lastUpdate,
|
||||||
|
newsFeed.lastUpdate,
|
||||||
alertsFeed.lastUpdate,
|
alertsFeed.lastUpdate,
|
||||||
smartMoneyFeed.lastUpdate,
|
smartMoneyFeed.lastUpdate,
|
||||||
classifierHitsFeed.lastUpdate
|
classifierHitsFeed.lastUpdate
|
||||||
|
|
@ -6861,6 +7060,8 @@ const useTerminalState = () => {
|
||||||
setReplaySource,
|
setReplaySource,
|
||||||
selectedAlert,
|
selectedAlert,
|
||||||
setSelectedAlert,
|
setSelectedAlert,
|
||||||
|
selectedNewsStory,
|
||||||
|
setSelectedNewsStory,
|
||||||
selectedDarkEvent,
|
selectedDarkEvent,
|
||||||
setSelectedDarkEvent,
|
setSelectedDarkEvent,
|
||||||
selectedClassifierHit,
|
selectedClassifierHit,
|
||||||
|
|
@ -6887,6 +7088,7 @@ const useTerminalState = () => {
|
||||||
equityJoins: equityJoinsFeed,
|
equityJoins: equityJoinsFeed,
|
||||||
nbbo: nbboFeed,
|
nbbo: nbboFeed,
|
||||||
inferredDark: inferredDarkFeed,
|
inferredDark: inferredDarkFeed,
|
||||||
|
news: newsFeed,
|
||||||
flow: flowFeed,
|
flow: flowFeed,
|
||||||
alerts: alertsFeed,
|
alerts: alertsFeed,
|
||||||
smartMoney: smartMoneyFeed,
|
smartMoney: smartMoneyFeed,
|
||||||
|
|
@ -6920,6 +7122,7 @@ const useTerminalState = () => {
|
||||||
equitiesScopedQuiet,
|
equitiesScopedQuiet,
|
||||||
equitiesSilentWarning,
|
equitiesSilentWarning,
|
||||||
filteredInferredDark,
|
filteredInferredDark,
|
||||||
|
filteredNews,
|
||||||
filteredFlow,
|
filteredFlow,
|
||||||
filteredAlerts,
|
filteredAlerts,
|
||||||
filteredSmartMoneyEvents,
|
filteredSmartMoneyEvents,
|
||||||
|
|
@ -6953,7 +7156,8 @@ const useTerminal = (): TerminalState => {
|
||||||
|
|
||||||
export const NAV_ITEMS = [
|
export const NAV_ITEMS = [
|
||||||
{ href: "/", label: "Home" },
|
{ href: "/", label: "Home" },
|
||||||
{ href: "/tape", label: "Tape" }
|
{ href: "/tape", label: "Tape" },
|
||||||
|
{ href: "/news", label: "News" }
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type PageFrameProps = {
|
type PageFrameProps = {
|
||||||
|
|
@ -7780,6 +7984,7 @@ const AlertsPane = memo(({ state, limit, withStrip = false, className }: AlertsP
|
||||||
data-tape-key={key}
|
data-tape-key={key}
|
||||||
style={{ transform: `translateY(${start}px)` }}
|
style={{ transform: `translateY(${start}px)` }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
state.setSelectedNewsStory(null);
|
||||||
state.setSelectedDarkEvent(null);
|
state.setSelectedDarkEvent(null);
|
||||||
state.setSelectedClassifierHit(null);
|
state.setSelectedClassifierHit(null);
|
||||||
state.setSelectedSmartMoneyEvent(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 (
|
||||||
|
<Pane
|
||||||
|
className={className}
|
||||||
|
title="News Wire"
|
||||||
|
status={
|
||||||
|
limit ? (
|
||||||
|
<Link className="terminal-button terminal-link-button" href="/news">
|
||||||
|
View all
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div className="status-inline status-connected">
|
||||||
|
<span className="status-dot" />
|
||||||
|
<span>{state.mode === "live" ? "Live wire" : "Live-only in v1"}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
canLoadOlder ? (
|
||||||
|
<button className="terminal-button" type="button" onClick={() => void state.liveSession.loadOlder("news")}>
|
||||||
|
Older
|
||||||
|
</button>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{state.mode === "replay" ? (
|
||||||
|
<div className="empty">News is live-only in v1.</div>
|
||||||
|
) : items.length === 0 ? (
|
||||||
|
<div className="empty">
|
||||||
|
{state.tickerSet.size > 0 ? "No news stories match the current filter." : "Waiting for live news stories."}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="news-list" role="list" aria-label="News stories">
|
||||||
|
{items.map((story) => (
|
||||||
|
<button
|
||||||
|
className="news-row"
|
||||||
|
key={`${story.trace_id}:${story.updated_ts}:${story.seq}`}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
state.setSelectedNewsStory(null);
|
||||||
|
state.setSelectedAlert(null);
|
||||||
|
state.setSelectedClassifierHit(null);
|
||||||
|
state.setSelectedSmartMoneyEvent(null);
|
||||||
|
state.setSelectedDarkEvent(null);
|
||||||
|
state.setSelectedNewsStory(story);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="news-row-head">
|
||||||
|
<h3>{story.headline}</h3>
|
||||||
|
<span className="news-row-time">{formatNewsTimestamp(story.published_ts)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="news-row-meta">
|
||||||
|
<span>{story.source}</span>
|
||||||
|
{story.resolved_symbols.map((symbol) => (
|
||||||
|
<span className="drawer-chip" key={`${story.trace_id}-${symbol}`}>
|
||||||
|
{symbol}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{!limit && story.summary ? <p className="drawer-note">{story.summary}</p> : null}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Pane>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
type ClassifierPaneProps = {
|
type ClassifierPaneProps = {
|
||||||
state: TerminalState;
|
state: TerminalState;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
|
|
@ -8016,6 +8298,7 @@ const DarkPane = memo(({ state, limit, className }: DarkPaneProps) => {
|
||||||
data-tape-key={key}
|
data-tape-key={key}
|
||||||
style={{ transform: `translateY(${start}px)` }}
|
style={{ transform: `translateY(${start}px)` }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
state.setSelectedNewsStory(null);
|
||||||
state.setSelectedAlert(null);
|
state.setSelectedAlert(null);
|
||||||
state.setSelectedClassifierHit(null);
|
state.setSelectedClassifierHit(null);
|
||||||
state.setSelectedSmartMoneyEvent(null);
|
state.setSelectedSmartMoneyEvent(null);
|
||||||
|
|
@ -8624,6 +8907,10 @@ export function TerminalAppShell({ children }: { children: ReactNode }) {
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{state.selectedNewsStory ? (
|
||||||
|
<NewsDrawer story={state.selectedNewsStory} onClose={() => state.setSelectedNewsStory(null)} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
{state.selectedClassifierHit ? (
|
{state.selectedClassifierHit ? (
|
||||||
<ClassifierHitDrawer
|
<ClassifierHitDrawer
|
||||||
hit={state.selectedClassifierHit}
|
hit={state.selectedClassifierHit}
|
||||||
|
|
@ -8662,12 +8949,24 @@ export function OverviewRoute() {
|
||||||
<div className="page-grid page-grid-home">
|
<div className="page-grid page-grid-home">
|
||||||
<ChartPane state={state} />
|
<ChartPane state={state} />
|
||||||
<EquitiesPane state={state} />
|
<EquitiesPane state={state} />
|
||||||
|
<NewsPane state={state} limit={6} />
|
||||||
<AlertsPane state={state} withStrip />
|
<AlertsPane state={state} withStrip />
|
||||||
</div>
|
</div>
|
||||||
</PageFrame>
|
</PageFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function TapeRoute() {
|
export function TapeRoute() {
|
||||||
const state = useTerminal();
|
const state = useTerminal();
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
3
apps/web/next-env.d.ts
vendored
3
apps/web/next-env.d.ts
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// 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.
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,14 @@
|
||||||
"@islandflow/types": "workspace:*",
|
"@islandflow/types": "workspace:*",
|
||||||
"@tanstack/react-virtual": "^3.13.24",
|
"@tanstack/react-virtual": "^3.13.24",
|
||||||
"lightweight-charts": "^4.2.0",
|
"lightweight-charts": "^4.2.0",
|
||||||
"next": "^14.2.4",
|
"next": "^16.2.6",
|
||||||
"react": "^18.3.1",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^19.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.10",
|
"@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"
|
"typescript": "^5.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
126
bun.lock
126
bun.lock
|
|
@ -26,13 +26,14 @@
|
||||||
"@islandflow/types": "workspace:*",
|
"@islandflow/types": "workspace:*",
|
||||||
"@tanstack/react-virtual": "^3.13.24",
|
"@tanstack/react-virtual": "^3.13.24",
|
||||||
"lightweight-charts": "^4.2.0",
|
"lightweight-charts": "^4.2.0",
|
||||||
"next": "^14.2.4",
|
"next": "^16.2.6",
|
||||||
"react": "^18.3.1",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^19.2.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.10",
|
"@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",
|
"typescript": "^5.5.4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -121,6 +122,17 @@
|
||||||
"zod": "^3.23.8",
|
"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": {
|
"services/ingest-options": {
|
||||||
"name": "@islandflow/ingest-options",
|
"name": "@islandflow/ingest-options",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -204,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=="],
|
"@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=="],
|
"@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/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=="],
|
"@inquirer/confirm": ["@inquirer/confirm@4.0.1", "", { "dependencies": { "@inquirer/core": "^9.2.1", "@inquirer/type": "^2.0.0" } }, "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w=="],
|
||||||
|
|
@ -250,6 +314,8 @@
|
||||||
|
|
||||||
"@islandflow/ingest-equities": ["@islandflow/ingest-equities@workspace:services/ingest-equities"],
|
"@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/ingest-options": ["@islandflow/ingest-options@workspace:services/ingest-options"],
|
||||||
|
|
||||||
"@islandflow/observability": ["@islandflow/observability@workspace:packages/observability"],
|
"@islandflow/observability": ["@islandflow/observability@workspace:packages/observability"],
|
||||||
|
|
@ -280,25 +346,23 @@
|
||||||
|
|
||||||
"@msgpack/msgpack": ["@msgpack/msgpack@3.1.3", "", {}, "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA=="],
|
"@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@16.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA=="],
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@14.2.33", "", { "os": "win32", "cpu": "x64" }, "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg=="],
|
|
||||||
|
|
||||||
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
|
|
@ -322,9 +386,7 @@
|
||||||
|
|
||||||
"@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
|
"@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.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="],
|
|
||||||
|
|
||||||
"@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="],
|
"@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="],
|
||||||
|
|
||||||
|
|
@ -352,9 +414,9 @@
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="],
|
"@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=="],
|
"@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],
|
||||||
|
|
||||||
|
|
@ -452,15 +514,13 @@
|
||||||
|
|
||||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
"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=="],
|
"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-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=="],
|
"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=="],
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
|
|
@ -700,8 +760,6 @@
|
||||||
|
|
||||||
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
"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-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||||
|
|
||||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
@ -732,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=="],
|
"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=="],
|
"lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
|
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
|
||||||
|
|
@ -790,7 +846,7 @@
|
||||||
|
|
||||||
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
"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=="],
|
"nice-try": ["nice-try@1.0.5", "", {}, "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="],
|
||||||
|
|
||||||
|
|
@ -884,9 +940,9 @@
|
||||||
|
|
||||||
"quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
|
|
@ -930,7 +986,7 @@
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
|
|
@ -940,6 +996,8 @@
|
||||||
|
|
||||||
"serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="],
|
"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-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=="],
|
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||||
|
|
@ -972,8 +1030,6 @@
|
||||||
|
|
||||||
"ssri": ["ssri@9.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q=="],
|
"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-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=="],
|
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||||
|
|
@ -986,7 +1042,7 @@
|
||||||
|
|
||||||
"strip-outer": ["strip-outer@1.0.1", "", { "dependencies": { "escape-string-regexp": "^1.0.2" } }, "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg=="],
|
"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=="],
|
"sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="],
|
||||||
|
|
||||||
|
|
@ -1128,8 +1184,6 @@
|
||||||
|
|
||||||
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
|
"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/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=="],
|
"cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||||
|
|
|
||||||
|
|
@ -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 compute/package.json ./services/compute/package.json
|
||||||
COPY --from=services eod-enricher/package.json ./services/eod-enricher/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-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/package.json ./services/ingest-options/package.json
|
||||||
COPY --from=services ingest-options/py/requirements.txt ./services/ingest-options/py/requirements.txt
|
COPY --from=services ingest-options/py/requirements.txt ./services/ingest-options/py/requirements.txt
|
||||||
COPY --from=services refdata/package.json ./services/refdata/package.json
|
COPY --from=services refdata/package.json ./services/refdata/package.json
|
||||||
|
|
|
||||||
|
|
@ -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 compute/package.json ./services/compute/package.json
|
||||||
COPY --from=services eod-enricher/package.json ./services/eod-enricher/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-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/package.json ./services/ingest-options/package.json
|
||||||
COPY --from=services refdata/package.json ./services/refdata/package.json
|
COPY --from=services refdata/package.json ./services/refdata/package.json
|
||||||
COPY --from=services replay/package.json ./services/replay/package.json
|
COPY --from=services replay/package.json ./services/replay/package.json
|
||||||
|
|
|
||||||
|
|
@ -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 compute/package.json ./services/compute/package.json
|
||||||
COPY --from=services eod-enricher/package.json ./services/eod-enricher/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-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/package.json ./services/ingest-options/package.json
|
||||||
COPY --from=services refdata/package.json ./services/refdata/package.json
|
COPY --from=services refdata/package.json ./services/refdata/package.json
|
||||||
COPY --from=services replay/package.json ./services/replay/package.json
|
COPY --from=services replay/package.json ./services/replay/package.json
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,10 @@ services:
|
||||||
<<: *service-common
|
<<: *service-common
|
||||||
command: ["services/ingest-equities/src/index.ts"]
|
command: ["services/ingest-equities/src/index.ts"]
|
||||||
|
|
||||||
|
ingest-news:
|
||||||
|
<<: *service-common
|
||||||
|
command: ["services/ingest-news/src/index.ts"]
|
||||||
|
|
||||||
replay:
|
replay:
|
||||||
<<: *service-common
|
<<: *service-common
|
||||||
profiles: ["replay"]
|
profiles: ["replay"]
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,14 @@
|
||||||
"@islandflow/types": "workspace:*",
|
"@islandflow/types": "workspace:*",
|
||||||
"@tanstack/react-virtual": "^3.13.24",
|
"@tanstack/react-virtual": "^3.13.24",
|
||||||
"lightweight-charts": "^4.2.0",
|
"lightweight-charts": "^4.2.0",
|
||||||
"next": "^14.2.4",
|
"next": "^16.2.6",
|
||||||
"react": "^18.3.1",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^19.2.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.10",
|
"@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",
|
"typescript": "^5.5.4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -121,6 +122,17 @@
|
||||||
"zod": "^3.23.8",
|
"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": {
|
"services/ingest-options": {
|
||||||
"name": "@islandflow/ingest-options",
|
"name": "@islandflow/ingest-options",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -204,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=="],
|
"@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=="],
|
"@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/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=="],
|
"@inquirer/confirm": ["@inquirer/confirm@4.0.1", "", { "dependencies": { "@inquirer/core": "^9.2.1", "@inquirer/type": "^2.0.0" } }, "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w=="],
|
||||||
|
|
@ -250,6 +314,8 @@
|
||||||
|
|
||||||
"@islandflow/ingest-equities": ["@islandflow/ingest-equities@workspace:services/ingest-equities"],
|
"@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/ingest-options": ["@islandflow/ingest-options@workspace:services/ingest-options"],
|
||||||
|
|
||||||
"@islandflow/observability": ["@islandflow/observability@workspace:packages/observability"],
|
"@islandflow/observability": ["@islandflow/observability@workspace:packages/observability"],
|
||||||
|
|
@ -280,25 +346,23 @@
|
||||||
|
|
||||||
"@msgpack/msgpack": ["@msgpack/msgpack@3.1.3", "", {}, "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA=="],
|
"@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@16.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA=="],
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@14.2.33", "", { "os": "win32", "cpu": "x64" }, "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg=="],
|
|
||||||
|
|
||||||
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
|
|
@ -322,9 +386,7 @@
|
||||||
|
|
||||||
"@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
|
"@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.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="],
|
|
||||||
|
|
||||||
"@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="],
|
"@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="],
|
||||||
|
|
||||||
|
|
@ -352,9 +414,9 @@
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="],
|
"@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=="],
|
"@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],
|
||||||
|
|
||||||
|
|
@ -452,15 +514,13 @@
|
||||||
|
|
||||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
"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=="],
|
"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-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=="],
|
"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=="],
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
|
|
@ -700,8 +760,6 @@
|
||||||
|
|
||||||
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
"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-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||||
|
|
||||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
@ -732,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=="],
|
"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=="],
|
"lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
|
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
|
||||||
|
|
@ -790,7 +846,7 @@
|
||||||
|
|
||||||
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
"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=="],
|
"nice-try": ["nice-try@1.0.5", "", {}, "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="],
|
||||||
|
|
||||||
|
|
@ -884,9 +940,9 @@
|
||||||
|
|
||||||
"quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
|
|
@ -930,7 +986,7 @@
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
|
|
@ -940,6 +996,8 @@
|
||||||
|
|
||||||
"serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="],
|
"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-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=="],
|
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||||
|
|
@ -972,8 +1030,6 @@
|
||||||
|
|
||||||
"ssri": ["ssri@9.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q=="],
|
"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-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=="],
|
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||||
|
|
@ -986,7 +1042,7 @@
|
||||||
|
|
||||||
"strip-outer": ["strip-outer@1.0.1", "", { "dependencies": { "escape-string-regexp": "^1.0.2" } }, "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg=="],
|
"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=="],
|
"sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="],
|
||||||
|
|
||||||
|
|
@ -1128,8 +1184,6 @@
|
||||||
|
|
||||||
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
|
"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/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=="],
|
"cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||||
|
|
|
||||||
566
docs/daily-git/2026-05-19-standup-summary-2026-05-18.html
Normal file
566
docs/daily-git/2026-05-19-standup-summary-2026-05-18.html
Normal file
|
|
@ -0,0 +1,566 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Daily Git Summary for 2026-05-18</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #06080b;
|
||||||
|
--bg-top: #091018;
|
||||||
|
--panel: rgba(17, 24, 32, 0.94);
|
||||||
|
--panel-strong: rgba(13, 20, 27, 0.98);
|
||||||
|
--border: rgba(255, 255, 255, 0.08);
|
||||||
|
--border-strong: rgba(245, 166, 35, 0.28);
|
||||||
|
--text: #e6edf4;
|
||||||
|
--muted: #90a0b2;
|
||||||
|
--faint: #6e7b8c;
|
||||||
|
--accent: #f5a623;
|
||||||
|
--accent-soft: rgba(245, 166, 35, 0.12);
|
||||||
|
--blue: #4da3ff;
|
||||||
|
--blue-soft: rgba(77, 163, 255, 0.12);
|
||||||
|
--green: #25c17a;
|
||||||
|
--green-soft: rgba(37, 193, 122, 0.12);
|
||||||
|
--red: #ff6b5f;
|
||||||
|
--red-soft: rgba(255, 107, 95, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top right, rgba(245, 166, 35, 0.1), transparent 24%),
|
||||||
|
linear-gradient(180deg, var(--bg-top) 0%, var(--bg) 28%, #05070a 100%);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(1120px, calc(100% - 40px));
|
||||||
|
margin: 32px auto 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero,
|
||||||
|
section {
|
||||||
|
background: linear-gradient(180deg, var(--panel), var(--panel-strong));
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
padding: 32px;
|
||||||
|
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow,
|
||||||
|
.pill,
|
||||||
|
.file-pill,
|
||||||
|
.commit-id,
|
||||||
|
.timestamp {
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow,
|
||||||
|
.pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
background: var(--accent-soft);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "Quantico", "Segoe UI", sans-serif;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 18px;
|
||||||
|
font-size: clamp(2rem, 3.5vw, 2.9rem);
|
||||||
|
line-height: 1.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.22rem;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 82ch;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero p {
|
||||||
|
margin-top: 14px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-grid,
|
||||||
|
.summary-grid,
|
||||||
|
.impact-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-grid,
|
||||||
|
.impact-grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-card,
|
||||||
|
.summary-card,
|
||||||
|
.impact-card,
|
||||||
|
.commit-card,
|
||||||
|
.snippet-card,
|
||||||
|
.callout {
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.025);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 24px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px 14px;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-id,
|
||||||
|
.timestamp {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-id {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
color: var(--faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
li + li {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-pill {
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: rgba(255, 255, 255, 0.045);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 0.73rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snippets {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 12px 0 0;
|
||||||
|
padding: 14px;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
background: rgba(6, 8, 11, 0.9);
|
||||||
|
color: #d8e4ef;
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tone-feature {
|
||||||
|
border-color: rgba(37, 193, 122, 0.24);
|
||||||
|
background: linear-gradient(180deg, rgba(37, 193, 122, 0.09), rgba(255, 255, 255, 0.02));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tone-docs {
|
||||||
|
border-color: rgba(77, 163, 255, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tone-risk {
|
||||||
|
border-color: rgba(255, 107, 95, 0.24);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 107, 95, 0.08), rgba(255, 255, 255, 0.02));
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #8bc0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
main {
|
||||||
|
width: min(100%, calc(100% - 24px));
|
||||||
|
margin: 20px auto 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero,
|
||||||
|
section {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<section class="hero">
|
||||||
|
<span class="eyebrow">Daily Git Summary</span>
|
||||||
|
<h1>Standup summary for Monday, May 18, 2026</h1>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<div class="meta-grid">
|
||||||
|
<div class="meta-card">
|
||||||
|
<span class="label">Commits</span>
|
||||||
|
<div class="metric">4</div>
|
||||||
|
</div>
|
||||||
|
<div class="meta-card">
|
||||||
|
<span class="label">Files Touched</span>
|
||||||
|
<div class="metric">35</div>
|
||||||
|
</div>
|
||||||
|
<div class="meta-card">
|
||||||
|
<span class="label">Insertions</span>
|
||||||
|
<div class="metric">1963</div>
|
||||||
|
</div>
|
||||||
|
<div class="meta-card">
|
||||||
|
<span class="label">Deletions</span>
|
||||||
|
<div class="metric">52</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<div class="summary-grid">
|
||||||
|
<div class="summary-card tone-feature">
|
||||||
|
<span class="label">Primary Delivery</span>
|
||||||
|
<p>
|
||||||
|
Commit <code>906fe411</code> added a new <code>services/ingest-news</code> service, news persistence in
|
||||||
|
<code>packages/storage</code>, API endpoints in <code>services/api</code>, and a live news view in
|
||||||
|
<code>apps/web/app/terminal.tsx</code> plus <code>apps/web/app/news/page.tsx</code>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card tone-docs">
|
||||||
|
<span class="label">Docs And Workflow</span>
|
||||||
|
<p>
|
||||||
|
Commits <code>62aae708</code>, <code>687a2170</code>, and <code>04baeceb</code> updated the previous standup
|
||||||
|
report, beads state, <code>deployment/docker/workspace-root/package.json</code>, and the repo-level
|
||||||
|
<code>AGENTS.md</code> instructions.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card">
|
||||||
|
<span class="label">Standup Framing</span>
|
||||||
|
<p>
|
||||||
|
Yesterday’s visible product work centered on making live Alpaca news available end to end. The remaining
|
||||||
|
activity was project hygiene and documentation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<div class="timeline">
|
||||||
|
<article class="commit-card tone-docs">
|
||||||
|
<div class="timeline-meta">
|
||||||
|
<strong>update beads</strong>
|
||||||
|
<span class="commit-id">687a2170</span>
|
||||||
|
<span class="timestamp">2026-05-18 03:15 -0400</span>
|
||||||
|
<span class="pill">1 file</span>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Added one line to <code>deployment/docker/workspace-root/package.json</code>. The local git history does not
|
||||||
|
show more context beyond the file touch and commit subject.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="commit-card tone-docs">
|
||||||
|
<div class="timeline-meta">
|
||||||
|
<strong>docs(general): add 2026-05-17 standup summary</strong>
|
||||||
|
<span class="commit-id">62aae708</span>
|
||||||
|
<span class="timestamp">2026-05-18 09:05 -0400</span>
|
||||||
|
<span class="pill">2 files</span>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Added the prior day’s report at <code>docs/general/2026-05-18-standup-summary-2026-05-17.html</code> and
|
||||||
|
updated <code>.beads/issues.jsonl</code>.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">docs/general/2026-05-18-standup-summary-2026-05-17.html</span>
|
||||||
|
<span class="file-pill">.beads/issues.jsonl</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="commit-card tone-feature">
|
||||||
|
<div class="timeline-meta">
|
||||||
|
<strong>add alpaca news wire across ingest api and web</strong>
|
||||||
|
<span class="commit-id">906fe411</span>
|
||||||
|
<span class="timestamp">2026-05-18 16:55 -0400</span>
|
||||||
|
<span class="pill">31 files</span>
|
||||||
|
<span class="pill">+1407 / -50</span>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Added a new ingest service in <code>services/ingest-news/src/index.ts</code> that backfills Alpaca news,
|
||||||
|
subscribes to the Alpaca news websocket, resolves symbols, and publishes <code>NewsStory</code> payloads to
|
||||||
|
NATS.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Extended shared contracts in <code>packages/types/src/events.ts</code> and
|
||||||
|
<code>packages/types/src/live.ts</code>, plus new storage support in
|
||||||
|
<code>packages/storage/src/news.ts</code> and <code>packages/storage/src/clickhouse.ts</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Wired the API to store, fan out, and expose news via <code>/news</code> and <code>/history/news</code> in
|
||||||
|
<code>services/api/src/index.ts</code> and live-session updates in <code>services/api/src/live.ts</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Added a web route in <code>apps/web/app/news/page.tsx</code>, a news pane and drawer in
|
||||||
|
<code>apps/web/app/terminal.tsx</code>, and related styling in <code>apps/web/app/globals.css</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Updated runtime packaging and local/dev deployment surfaces, including
|
||||||
|
<code>deployment/docker/docker-compose.yml</code>, Dockerfiles, <code>scripts/dev.ts</code>, and
|
||||||
|
<code>scripts/deploy.ts</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Added tests in <code>packages/storage/tests/news.test.ts</code>,
|
||||||
|
<code>services/ingest-news/tests/symbols.test.ts</code>, and adjusted
|
||||||
|
<code>apps/web/app/terminal.test.ts</code> plus <code>packages/types/tests/live.test.ts</code>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">services/ingest-news/src/index.ts</span>
|
||||||
|
<span class="file-pill">packages/storage/src/news.ts</span>
|
||||||
|
<span class="file-pill">services/api/src/index.ts</span>
|
||||||
|
<span class="file-pill">apps/web/app/terminal.tsx</span>
|
||||||
|
<span class="file-pill">apps/web/app/news/page.tsx</span>
|
||||||
|
<span class="file-pill">apps/web/app/globals.css</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="commit-card tone-docs">
|
||||||
|
<div class="timeline-meta">
|
||||||
|
<strong>update turn docs and beads workflow</strong>
|
||||||
|
<span class="commit-id">04baeceb</span>
|
||||||
|
<span class="timestamp">2026-05-18 21:32 -0400</span>
|
||||||
|
<span class="pill">1 file</span>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Edited <code>AGENTS.md</code> to update turn-document and beads workflow guidance.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
This summary is based on local git history between <code>2026-05-18 00:00 -0400</code> and
|
||||||
|
<code>2026-05-19 00:00 -0400</code>. 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.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<div class="snippets">
|
||||||
|
<article class="snippet-card">
|
||||||
|
<h3>News ingestion was introduced as a first-class service</h3>
|
||||||
|
<p>
|
||||||
|
<code>services/ingest-news/src/index.ts</code> authenticates against Alpaca, backfills recent news, subscribes
|
||||||
|
to live updates, resolves symbols, validates payloads with <code>NewsStorySchema</code>, and publishes them onto
|
||||||
|
the repo’s bus layer.
|
||||||
|
</p>
|
||||||
|
<pre><code>const backfill = await fetchBackfill();
|
||||||
|
for (const item of backfill.reverse()) {
|
||||||
|
await publishStory(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg === "authenticated") {
|
||||||
|
ws.send(JSON.stringify({ action: "subscribe", news: ["*"] }));
|
||||||
|
}</code></pre>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="snippet-card">
|
||||||
|
<h3>API and live session support were expanded for news</h3>
|
||||||
|
<p>
|
||||||
|
<code>services/api/src/index.ts</code> now ensures the news table exists, subscribes to a news consumer, fans
|
||||||
|
out live updates, and exposes both recent and paginated history endpoints.
|
||||||
|
</p>
|
||||||
|
<pre><code>if (req.method === "GET" && url.pathname === "/news") {
|
||||||
|
const limit = parseLimit(url.searchParams.get("limit") ?? "100");
|
||||||
|
const data = await fetchRecentNews(clickhouse, limit);
|
||||||
|
return jsonResponse({ data });
|
||||||
|
}</code></pre>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="snippet-card">
|
||||||
|
<h3>The web terminal gained a dedicated news surface</h3>
|
||||||
|
<p>
|
||||||
|
<code>apps/web/app/terminal.tsx</code> added a live-only news pane, a per-story drawer, history loading, and a
|
||||||
|
new <code>/news</code> route entry point via <code>apps/web/app/news/page.tsx</code>.
|
||||||
|
</p>
|
||||||
|
<pre><code>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>
|
||||||
|
);
|
||||||
|
}</code></pre>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<div class="impact-grid">
|
||||||
|
<div class="impact-card">
|
||||||
|
<span class="label">Live Terminal</span>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="impact-card">
|
||||||
|
<span class="label">Coverage</span>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="impact-card tone-risk">
|
||||||
|
<span class="label">Current Boundary</span>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Reviewed local git history with <code>git log --since='2026-05-18 00:00' --until='2026-05-19 00:00'</code>.</li>
|
||||||
|
<li>Used <code>git log --stat</code>, <code>git show</code>, and file-level history to anchor each summary item to specific commits and files.</li>
|
||||||
|
<li>No builds or tests were run for this reporting task because the work product is a git summary document, not a behavior change.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
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.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The <code>update beads</code> commit touched only <code>deployment/docker/workspace-root/package.json</code> in
|
||||||
|
visible git output, so this report does not infer intent beyond that recorded file change.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Counts here describe May 18 commits only and exclude any uncommitted work present after that date.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
No new product follow-up items were derived from this reporting pass. The only beads item created for this task
|
||||||
|
is <code>islandflow-2df</code>, which tracks publication of this summary document.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
549
docs/general/2026-05-18-standup-summary-2026-05-17.html
Normal file
549
docs/general/2026-05-18-standup-summary-2026-05-17.html
Normal file
|
|
@ -0,0 +1,549 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Standup Summary for 2026-05-17</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #06080b;
|
||||||
|
--panel: #111820;
|
||||||
|
--panel-2: #0d141b;
|
||||||
|
--border: rgba(255, 255, 255, 0.08);
|
||||||
|
--border-strong: rgba(245, 166, 35, 0.35);
|
||||||
|
--text: #e6edf4;
|
||||||
|
--muted: #90a0b2;
|
||||||
|
--faint: #6e7b8c;
|
||||||
|
--accent: #f5a623;
|
||||||
|
--accent-soft: rgba(245, 166, 35, 0.12);
|
||||||
|
--green: #25c17a;
|
||||||
|
--green-soft: rgba(37, 193, 122, 0.12);
|
||||||
|
--blue: #4da3ff;
|
||||||
|
--blue-soft: rgba(77, 163, 255, 0.12);
|
||||||
|
--red: #ff6b5f;
|
||||||
|
--red-soft: rgba(255, 107, 95, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top right, rgba(245, 166, 35, 0.12), transparent 28%),
|
||||||
|
linear-gradient(180deg, #091018 0%, var(--bg) 28%, #05070a 100%);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(1120px, calc(100% - 40px));
|
||||||
|
margin: 32px auto 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero,
|
||||||
|
section {
|
||||||
|
background: linear-gradient(180deg, rgba(17, 24, 32, 0.94), rgba(13, 20, 27, 0.98));
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
padding: 32px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow,
|
||||||
|
.pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
background: var(--accent-soft);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill {
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "Quantico", "Segoe UI", sans-serif;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 18px;
|
||||||
|
font-size: clamp(2rem, 4vw, 3rem);
|
||||||
|
line-height: 1.04;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 80ch;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero p {
|
||||||
|
margin-top: 14px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-card {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-card strong,
|
||||||
|
.label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 24px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-list,
|
||||||
|
.detail-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-item,
|
||||||
|
.timeline-item,
|
||||||
|
.callout {
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.025);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-item strong,
|
||||||
|
.timeline-item strong {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px 14px;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-id,
|
||||||
|
.timestamp {
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit-id {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
color: var(--faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
li + li {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-pill {
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.045);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.good {
|
||||||
|
background: var(--green-soft);
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
background: var(--blue-soft);
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk {
|
||||||
|
background: var(--red-soft);
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.92em;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-two {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<article class="hero">
|
||||||
|
<span class="eyebrow">Git Standup Summary</span>
|
||||||
|
<h1>Repository activity recorded for 2026-05-17</h1>
|
||||||
|
<p>
|
||||||
|
Yesterday's git history shows three main themes: frontend and API work to hydrate alert evidence from
|
||||||
|
ClickHouse, deploy workflow changes in <code>scripts/deploy.ts</code>, 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.
|
||||||
|
</p>
|
||||||
|
<div class="meta">
|
||||||
|
<div class="meta-card">
|
||||||
|
<strong>Commit Count</strong>
|
||||||
|
<span>20 commits on 2026-05-17</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-card">
|
||||||
|
<strong>Merges</strong>
|
||||||
|
<span>5 pull request merges</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-card">
|
||||||
|
<strong>File Footprint</strong>
|
||||||
|
<span>22 distinct paths touched</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-card">
|
||||||
|
<strong>Most Revisited</strong>
|
||||||
|
<span><code>.beads/issues.jsonl</code>, <code>scripts/deploy.ts</code>, <code>apps/web/app/terminal.tsx</code></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<div class="summary-list">
|
||||||
|
<div class="summary-item">
|
||||||
|
<strong>Alert context from ClickHouse landed and was merged twice through follow-up PRs.</strong>
|
||||||
|
The core implementation appeared in commit <code>c0b5b6d</code> and merge PR <code>#41</code>
|
||||||
|
(<code>3e08955</code>), then was extended in <code>58e57fa</code> and merged through <code>#43</code>
|
||||||
|
(<code>a27d499</code>) and a documentation polish PR <code>#44</code> (<code>49efc24</code>).
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<strong>Deploy tooling changed in three steps.</strong>
|
||||||
|
The day included an allowlist tightening in <code>5ddfbfa</code>, a new fast deploy mode in
|
||||||
|
<code>75ed6f3</code>, and Forgejo-aware remote resolution in <code>6e6788b</code>, all centered on
|
||||||
|
<code>scripts/deploy.ts</code>.
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<strong>Process and reporting work was visible alongside feature work.</strong>
|
||||||
|
Beads Dolt remote configuration was added in <code>37bd393</code>, revised in <code>d0d8bd4</code> and
|
||||||
|
<code>cd0a1dd</code>, and yesterday's prior standup report was added in <code>0416194</code>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<div class="timeline">
|
||||||
|
<article class="timeline-item">
|
||||||
|
<div class="timeline-meta">
|
||||||
|
<span class="pill info">Frontend + API</span>
|
||||||
|
<span class="commit-id">c0b5b6d</span>
|
||||||
|
<span class="timestamp">11:02 EDT</span>
|
||||||
|
</div>
|
||||||
|
<h3>Hydrate alert evidence from ClickHouse</h3>
|
||||||
|
<p>
|
||||||
|
Commit <code>c0b5b6d</code> added ClickHouse-backed alert context across storage, API, tests, and the
|
||||||
|
terminal UI. The same change set was merged as PR <code>#41</code> in <code>3e08955</code>.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">packages/storage/src/clickhouse.ts</span>
|
||||||
|
<span class="file-pill">services/api/src/alert-context.ts</span>
|
||||||
|
<span class="file-pill">services/api/src/index.ts</span>
|
||||||
|
<span class="file-pill">apps/web/app/terminal.tsx</span>
|
||||||
|
<span class="file-pill">apps/web/app/terminal.test.ts</span>
|
||||||
|
<span class="file-pill">packages/storage/tests/alerts.test.ts</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="timeline-item">
|
||||||
|
<div class="timeline-meta">
|
||||||
|
<span class="pill">Deploy workflow</span>
|
||||||
|
<span class="commit-id">5ddfbfa</span>
|
||||||
|
<span class="timestamp">11:45 EDT</span>
|
||||||
|
</div>
|
||||||
|
<h3>Tighten deploy remote untracked allowlist</h3>
|
||||||
|
<p>
|
||||||
|
Commit <code>5ddfbfa</code>, later merged as PR <code>#42</code> in <code>8b166a5</code>, narrowed the
|
||||||
|
remote untracked allowlist in <code>scripts/deploy.ts</code>. Two follow-up documentation commits,
|
||||||
|
<code>8631a53</code> and <code>219d3fd</code>, recorded and corrected the validation notes for that
|
||||||
|
change.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">scripts/deploy.ts</span>
|
||||||
|
<span class="file-pill">docs/turns/2026-05-17-deploy-allowlist-pr-packaging.html</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="timeline-item">
|
||||||
|
<div class="timeline-meta">
|
||||||
|
<span class="pill info">Integration</span>
|
||||||
|
<span class="commit-id">58e57fa</span>
|
||||||
|
<span class="timestamp">20:18 EDT</span>
|
||||||
|
</div>
|
||||||
|
<h3>Add ClickHouse alert context hydration for alert drawers</h3>
|
||||||
|
<p>
|
||||||
|
Commit <code>58e57fa</code> extended the earlier alert-context work, adding drawer-specific hydration in
|
||||||
|
the web app and API. A merge-conflict resolution commit <code>dc932cf</code> combined this with the
|
||||||
|
deploy allowlist branch before PR <code>#43</code> merged in <code>a27d499</code>.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">apps/web/app/terminal.tsx</span>
|
||||||
|
<span class="file-pill">packages/storage/src/clickhouse.ts</span>
|
||||||
|
<span class="file-pill">services/api/src/index.ts</span>
|
||||||
|
<span class="file-pill">docs/turns/2026-05-17-clickhouse-alert-context.html</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="timeline-item">
|
||||||
|
<div class="timeline-meta">
|
||||||
|
<span class="pill">Deploy workflow</span>
|
||||||
|
<span class="commit-id">75ed6f3</span>
|
||||||
|
<span class="timestamp">22:53 EDT</span>
|
||||||
|
</div>
|
||||||
|
<h3>Add fast deploy mode for routine rollouts</h3>
|
||||||
|
<p>
|
||||||
|
Commit <code>75ed6f3</code> added a faster deploy path and updated both deployment readmes. Minutes
|
||||||
|
later, commit <code>6e6788b</code> made deploy remote resolution Forgejo-aware, again in
|
||||||
|
<code>scripts/deploy.ts</code>.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">scripts/deploy.ts</span>
|
||||||
|
<span class="file-pill">deployment/docker/README.md</span>
|
||||||
|
<span class="file-pill">deployment/native/README.md</span>
|
||||||
|
<span class="file-pill">docs/turns/2026-05-17-add-fast-deploy-mode.html</span>
|
||||||
|
<span class="file-pill">docs/turns/2026-05-17-forgejo-deploy-remote-resolution.html</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="timeline-item">
|
||||||
|
<div class="timeline-meta">
|
||||||
|
<span class="pill">Repo operations</span>
|
||||||
|
<span class="commit-id">37bd393</span>
|
||||||
|
<span class="timestamp">06:41 EDT</span>
|
||||||
|
</div>
|
||||||
|
<h3>Beads remote setup and daily reporting</h3>
|
||||||
|
<p>
|
||||||
|
Commit <code>37bd393</code> configured the Beads Dolt remote in <code>.beads/config.yaml</code>, then
|
||||||
|
commits <code>d0d8bd4</code> and <code>cd0a1dd</code> revised the same sync settings. Commit
|
||||||
|
<code>0416194</code> added the standup summary document for 2026-05-16 activity in
|
||||||
|
<code>docs/general</code>.
|
||||||
|
</p>
|
||||||
|
<div class="file-list">
|
||||||
|
<span class="file-pill">.beads/config.yaml</span>
|
||||||
|
<span class="file-pill">.beads/issues.jsonl</span>
|
||||||
|
<span class="file-pill">docs/general/2026-05-17-standup-summary-2026-05-16.html</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<div class="grid-two">
|
||||||
|
<div class="callout">
|
||||||
|
<span class="label">Merged PRs</span>
|
||||||
|
<ul>
|
||||||
|
<li><code>#40</code> merged in <code>88b2c33</code>: live tape scroll stability and related deploy/image work.</li>
|
||||||
|
<li><code>#41</code> merged in <code>3e08955</code>: initial ClickHouse alert evidence hydration.</li>
|
||||||
|
<li><code>#42</code> merged in <code>8b166a5</code>: deploy allowlist packaging follow-through.</li>
|
||||||
|
<li><code>#43</code> merged in <code>a27d499</code>: alert drawer hydration follow-up.</li>
|
||||||
|
<li><code>#44</code> merged in <code>49efc24</code>: turn-document polish for alert context.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="callout">
|
||||||
|
<span class="label">Most Touched Areas</span>
|
||||||
|
<ul>
|
||||||
|
<li><code>.beads/issues.jsonl</code> changed in 9 commits, reflecting issue tracking churn throughout the day.</li>
|
||||||
|
<li><code>scripts/deploy.ts</code> changed in 3 direct commits tied to deploy safety and speed.</li>
|
||||||
|
<li><code>apps/web/app/terminal.tsx</code> changed in 3 direct commits tied to live tape behavior and alert context.</li>
|
||||||
|
<li>Documentation output expanded across <code>docs/turns</code> and <code>docs/general</code> alongside implementation work.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
The ClickHouse alert-context work was not isolated to one layer. Commits <code>c0b5b6d</code> and
|
||||||
|
<code>58e57fa</code> touched storage access, API wiring, UI presentation, and dedicated tests, which
|
||||||
|
makes this the clearest full-stack change in yesterday's history.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The deploy changes were incremental rather than a single rewrite. The history shows a narrowing change in
|
||||||
|
<code>5ddfbfa</code>, an operator-speed path in <code>75ed6f3</code>, and remote detection logic in
|
||||||
|
<code>6e6788b</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Merge commit <code>dc932cf</code> 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.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Commit <code>073c1de</code> created an empty <code>forgejo.test</code> path. The git history shows the file
|
||||||
|
creation, but no test content in that commit.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
User-facing terminal behavior changed in two visible ways: live tape scroll stability from
|
||||||
|
<code>d334e16</code>/<code>#40</code> and richer alert evidence context from <code>c0b5b6d</code>,
|
||||||
|
<code>58e57fa</code>, and the follow-up merges.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Deploy workflow commits affected operator tooling rather than customer-facing product screens. Those changes
|
||||||
|
should matter most to maintainers using <code>scripts/deploy.ts</code> and the deployment readmes.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Beads remote configuration and standup-report commits affected internal workflow and documentation, not
|
||||||
|
runtime product behavior.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
The turn document added in <code>d334e16</code> records
|
||||||
|
<code>bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts</code> passing and
|
||||||
|
<code>bun --cwd=apps/web run build</code> passing.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The turn document added in <code>c0b5b6d</code> records
|
||||||
|
<code>bun test packages/storage/tests</code>,
|
||||||
|
<code>bun test services/api/tests</code>,
|
||||||
|
<code>bun test apps/web/app/terminal.test.ts</code>, and
|
||||||
|
<code>bun --cwd=apps/web run build</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The polished turn document merged in <code>49efc24</code> records those alert-context validations as
|
||||||
|
passing.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The deploy allowlist turn document created in <code>8631a53</code> and corrected in <code>219d3fd</code>
|
||||||
|
explicitly notes that a repository-wide <code>bun test</code> run reported failures at that point.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Later deploy-related turn documents added in <code>75ed6f3</code> and <code>6e6788b</code> record full
|
||||||
|
<code>bun test</code> passing, with the Forgejo remote document stating <code>232 passing</code>,
|
||||||
|
<code>0 failing</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
This automation run only created documentation. No additional code validation command was run for this
|
||||||
|
summary itself.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
This document summarizes repository history only. It does not infer goals beyond what commit subjects, PR
|
||||||
|
titles, merge structure, and touched files show.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Some PR context is visible only through merge commits. For example, PR <code>#40</code> 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.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Validation evidence comes from committed turn documents, not from re-running every historical command during
|
||||||
|
this automation.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<p>
|
||||||
|
No new follow-up Beads issue was created from the git summary itself. The Beads task for this automation run
|
||||||
|
is <code>islandflow-x70</code>, which tracks creation of this standup document and will be closed as part of
|
||||||
|
the session sync.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
152
docs/turns/2026-05-18-news-wire-view.html
Normal file
152
docs/turns/2026-05-18-news-wire-view.html
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Turn Report: News Wire View via Alpaca Feed</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #0b1016;
|
||||||
|
--panel: #111820;
|
||||||
|
--panel-2: #0d141b;
|
||||||
|
--border: rgba(255, 255, 255, 0.08);
|
||||||
|
--text: #e6edf4;
|
||||||
|
--dim: #90a0b2;
|
||||||
|
--accent: #f5a623;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 32px;
|
||||||
|
background: linear-gradient(180deg, #06080b 0%, #0b1016 100%);
|
||||||
|
color: var(--text);
|
||||||
|
font: 15px/1.6 "IBM Plex Sans", sans-serif;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 28px;
|
||||||
|
}
|
||||||
|
h1, h2 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-family: "Quantico", sans-serif;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
h1 { font-size: 1.8rem; }
|
||||||
|
h2 { font-size: 1rem; margin-top: 28px; }
|
||||||
|
p, li { color: var(--text); }
|
||||||
|
.summary {
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid rgba(245, 166, 35, 0.28);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(245, 166, 35, 0.08);
|
||||||
|
}
|
||||||
|
.meta, code, pre { font-family: "IBM Plex Mono", monospace; }
|
||||||
|
.meta { color: var(--dim); font-size: 0.85rem; }
|
||||||
|
section {
|
||||||
|
padding-top: 4px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
section:first-of-type { border-top: 0; }
|
||||||
|
ul { padding-left: 18px; }
|
||||||
|
pre {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--panel-2);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
a { color: var(--accent); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<p class="meta">Created 2026-05-18 · Task: News Wire View via Alpaca Feed</p>
|
||||||
|
<h1>News Wire View via Alpaca Feed</h1>
|
||||||
|
<div class="summary">
|
||||||
|
<strong>Summary</strong>
|
||||||
|
<p>
|
||||||
|
Added an Alpaca-backed live news pipeline end to end: normalized <code>NewsStory</code> types,
|
||||||
|
a dedicated JetStream subject/stream, ClickHouse storage helpers with latest-revision semantics,
|
||||||
|
a new <code>services/ingest-news</code> service, API endpoints and live fanout, and a web
|
||||||
|
<code>/news</code> route plus Home preview with a right-side story drawer.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Added <code>NewsStorySchema</code>, the <code>news</code> live channel, and subscription parsing support in <code>packages/types</code>.</li>
|
||||||
|
<li>Added bus constants for the <code>flow.news</code> subject and <code>NEWS</code> stream.</li>
|
||||||
|
<li>Added ClickHouse news storage helpers, including recent, before-cursor, and after-cursor queries that collapse provider revisions to the latest row per <code>provider + story_id</code>.</li>
|
||||||
|
<li>Created <code>services/ingest-news</code> with Alpaca REST backfill, Alpaca websocket streaming, normalization, and deterministic ticker resolution.</li>
|
||||||
|
<li>Extended the API service to persist live news in the shared cache, expose <code>GET /news</code> and <code>GET /history/news</code>, and fan out <code>news</code> events on <code>/ws/live</code>.</li>
|
||||||
|
<li>Added a top-level <code>/news</code> route, primary nav entry, Home preview pane, replay-mode live-only empty states, and a sanitized full-story drawer.</li>
|
||||||
|
<li>Updated dev and deployment wiring so the new service is included in local runners and the Docker workspace snapshot.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Ticker resolution prefers provider symbols first, then falls back only to structured patterns in provider HTML: ticker anchors, <code>EXCHANGE:SYM</code>, and <code>$SYM</code>.</li>
|
||||||
|
<li>News history uses <code>published_ts</code> as the visible cursor while revisions are collapsed with a window function over <code>provider, story_id</code> ordered by <code>updated_ts</code>, <code>ingest_ts</code>, and <code>seq</code>.</li>
|
||||||
|
<li>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.</li>
|
||||||
|
<li>Replay mode intentionally renders a clear empty state for news on both Home and <code>/news</code> instead of pretending news is replay-synced.</li>
|
||||||
|
</ul>
|
||||||
|
<pre><code>resolved_symbols = provider_symbols
|
||||||
|
or ticker anchors in content_html
|
||||||
|
or EXCHANGE:SYM matches
|
||||||
|
or $SYM matches</code></pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Ran targeted Bun tests covering types, storage, API live-state behavior, ingest-news symbol resolution, route wiring, and terminal helpers.</li>
|
||||||
|
<li>Built the Next.js web app with <code>bun --cwd=apps/web run build</code>.</li>
|
||||||
|
<li>Ran <code>bun run check:docker-workspace</code> after syncing the deployment workspace snapshot.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Replay support remains intentionally absent in v1; the UI now states that explicitly instead of showing misleading empty historical behavior.</li>
|
||||||
|
<li>The sanitizer is intentionally conservative and custom, which keeps dependencies light but may strip some harmless provider formatting.</li>
|
||||||
|
<li>The ingest service assumes Alpaca’s current REST and websocket news contracts; if Alpaca changes those payload shapes, the normalization layer will need adjustment.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<ul>
|
||||||
|
<li>No additional follow-up issue was required during this turn.</li>
|
||||||
|
<li>Future extensions are still available behind the same contract: multi-provider aggregation, server-side symbol filtering, and replay-aware news history.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
259
docs/turns/2026-05-19-0739-update-readme-current-state.html
Normal file
259
docs/turns/2026-05-19-0739-update-readme-current-state.html
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>README Current-State Update</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #17131f;
|
||||||
|
--panel: #231b31;
|
||||||
|
--panel-2: #2c223d;
|
||||||
|
--text: #f3edf8;
|
||||||
|
--muted: #cbbdda;
|
||||||
|
--line: #514160;
|
||||||
|
--lavender: #c9a7ff;
|
||||||
|
--pink: #f0a7d7;
|
||||||
|
--code: #120e18;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Sans", Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
line-height: 1.62;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(980px, calc(100% - 40px));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 48px 0 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
margin-bottom: 32px;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
color: var(--pink);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 8px 0 12px;
|
||||||
|
font-size: clamp(2rem, 4vw, 3.4rem);
|
||||||
|
line-height: 1.08;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: var(--lavender);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 18px 0;
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li + li {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: var(--code);
|
||||||
|
border: 1px solid #3a2d49;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #f4c4e2;
|
||||||
|
padding: 0.12rem 0.32rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow-x: auto;
|
||||||
|
background: var(--code);
|
||||||
|
border: 1px solid #3a2d49;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #eadff2;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
color: var(--muted);
|
||||||
|
max-width: 72ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillrow {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill {
|
||||||
|
background: var(--panel-2);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 999px;
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--pink);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<header>
|
||||||
|
<div class="eyebrow">Turn document · 2026-05-19 07:39 America/New_York</div>
|
||||||
|
<h1>README Current-State Update</h1>
|
||||||
|
<p class="summary">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<div class="pillrow">
|
||||||
|
<span class="pill">README.md</span>
|
||||||
|
<span class="pill">smart-money taxonomy</span>
|
||||||
|
<span class="pill">Next.js 16.2.6</span>
|
||||||
|
<span class="pill">deployment docs</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Resolved the conflicted README by preserving the useful project-state content and removing stale simplified sections.</li>
|
||||||
|
<li>Added a first-class smart-money taxonomy section for the six public profiles: <code>institutional_directional</code>, <code>retail_whale</code>, <code>event_driven</code>, <code>vol_seller</code>, <code>arbitrage</code>, and <code>hedge_reactive</code>.</li>
|
||||||
|
<li>Documented that smart-money events are now the semantic object, while legacy classifier hits and alerts remain compatibility surfaces.</li>
|
||||||
|
<li>Updated the current implementation state to include Alpaca news ingest, profile-aware UI behavior, alert-context hydration, and the Electron shell.</li>
|
||||||
|
<li>Recorded the Next.js update to <code>16.2.6</code> with React and React DOM <code>19.2.0</code>.</li>
|
||||||
|
<li>Clarified deployment: Docker is still the supported VPS path, native Bun/systemd rollout is experimental, and scoped deploy flags are available.</li>
|
||||||
|
<li>Aligned live-cache and web hot-window defaults with the current env examples and API defaults.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<p>
|
||||||
|
The README intentionally treats <code>FlowPacket</code> as an intermediate clustering bridge and <code>SmartMoneyEvent</code> 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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Deployment language now matches the current operations docs: <code>./deploy main</code> defaults to the Docker path, <code>--runtime native</code> is available but experimental, and native rollout still depends on systemd units and reverse-proxy preparation.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Relevant Diff Snippets</h2>
|
||||||
|
<p>
|
||||||
|
Diff snippets are formatted for readability in the same spirit as <a href="https://diffs.com/docs">diffs.com</a>, with only the most relevant README changes shown here.
|
||||||
|
</p>
|
||||||
|
<pre><code>+## 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 |</code></pre>
|
||||||
|
<pre><code>+## 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</code></pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Confirmed no merge conflict markers remain with <code>rg -n "<<<<<<<|=======|>>>>>>>" README.md</code>.</li>
|
||||||
|
<li>Ran <code>git diff --check</code>; no whitespace or patch-format issues were reported.</li>
|
||||||
|
<li>Ran focused tests: <code>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</code>.</li>
|
||||||
|
<li>Focused test result: 12 pass, 0 fail.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>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.</li>
|
||||||
|
<li>The README summarizes environment variables instead of listing every low-level classifier and dark-inference threshold. Detailed knobs remain available in <code>.env.example</code> and service code.</li>
|
||||||
|
<li>Native deployment remains experimental; the README calls that out directly and points to the dedicated native deployment document.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>islandflow-38p</code>: add native deployment unit templates and rollback helpers.</li>
|
||||||
|
<li><code>islandflow-932</code>: continue desktop follow-up native features.</li>
|
||||||
|
<li><code>islandflow-2db</code>: manually remove stale local-infra containers from the VPS when doing server hygiene.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
200
docs/turns/2026-05-19-0805-clarify-repo-turn-doc-rules.html
Normal file
200
docs/turns/2026-05-19-0805-clarify-repo-turn-doc-rules.html
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Clarify Repo Turn Documentation Rules</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: light;
|
||||||
|
--paper: #f7f3ec;
|
||||||
|
--ink: #1f211c;
|
||||||
|
--muted: #666b5d;
|
||||||
|
--line: #d8d0c1;
|
||||||
|
--accent: #405f54;
|
||||||
|
--accent-soft: #e4ece5;
|
||||||
|
--code-bg: #eee7db;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--paper);
|
||||||
|
color: var(--ink);
|
||||||
|
font: 16px/1.62 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
max-width: 920px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 48px 28px 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
margin-bottom: 34px;
|
||||||
|
padding-bottom: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(2.1rem, 5vw, 4rem);
|
||||||
|
letter-spacing: 0;
|
||||||
|
line-height: 1.02;
|
||||||
|
margin: 10px 0 14px;
|
||||||
|
max-width: 11ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 1rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
max-width: 72ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
padding: 22px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li + li {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: var(--code-bg);
|
||||||
|
border-radius: 4px;
|
||||||
|
font: 0.92em ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
padding: 0.12rem 0.32rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: var(--code-bg);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 1.08rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
background: var(--accent-soft);
|
||||||
|
border: 1px solid #c7d8cc;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<header>
|
||||||
|
<div class="eyebrow">Turn document · 2026-05-19 08:05 America/New_York</div>
|
||||||
|
<h1>Clarify Repo Turn Documentation Rules</h1>
|
||||||
|
<p class="summary">
|
||||||
|
Updated the repository instructions so Islandflow turn documents are clearly repo-local and styled through <code>impeccable</code>, without inheriting global non-repo computer-task styling.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>
|
||||||
|
The repo <code>AGENTS.md</code> now removes a stray non-repo location rule and explicitly states that <code>impeccable</code> is the styling and layout authority for Islandflow turn documents when available.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Removed the confusing instruction to save non-repo documentation under <code>~/dev/docs/turns/</code>.</li>
|
||||||
|
<li>Clarified that repository turn documents stay in <code>docs/turns/</code>.</li>
|
||||||
|
<li>Updated the format rule to say <code>impeccable</code> handles both structure and styling.</li>
|
||||||
|
<li>Added an explicit guard against applying global non-repo computer-task house styling to this repository's turn documents.</li>
|
||||||
|
<li>Clarified that the fallback standalone HTML path only applies when <code>impeccable</code> is unavailable or blocked by an actual error.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<p>
|
||||||
|
This was a documentation-only change in <code>AGENTS.md</code>. It changes future agent behavior but does not alter runtime code, tests, deployment scripts, or application behavior.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Relevant Diff Snippets</h2>
|
||||||
|
<pre><code>-## 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:</code></pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<p>
|
||||||
|
Future Islandflow turns should produce documentation in the repo's <code>docs/turns/</code> folder and let <code>impeccable</code> drive the visual treatment, making repo documentation less likely to inherit global computer-task styling.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Reviewed the <code>AGENTS.md</code> diff after patching.</li>
|
||||||
|
<li>Ran <code>git diff --check</code> with no whitespace errors.</li>
|
||||||
|
<li>No application test suite was run because this change only updates repository instructions.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<p class="note">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<p>
|
||||||
|
No follow-up issue is required for this patch. The related Beads task for this documentation cleanup is <code>islandflow-lm6</code>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
276
docs/turns/2026-05-19-reconcile-pr-conflicts.html
Normal file
276
docs/turns/2026-05-19-reconcile-pr-conflicts.html
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Reconcile PR Conflicts</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: oklch(13% 0.018 252);
|
||||||
|
--panel: oklch(18% 0.022 248);
|
||||||
|
--panel-2: oklch(22% 0.026 248);
|
||||||
|
--line: oklch(72% 0.03 248 / 0.18);
|
||||||
|
--text: oklch(93% 0.012 248);
|
||||||
|
--muted: oklch(72% 0.025 248);
|
||||||
|
--faint: oklch(58% 0.025 248);
|
||||||
|
--amber: oklch(78% 0.16 72);
|
||||||
|
--green: oklch(73% 0.16 154);
|
||||||
|
--blue: oklch(70% 0.14 245);
|
||||||
|
--red: oklch(68% 0.18 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(1120px, calc(100% - 32px));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 48px 0 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
padding-bottom: 28px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
color: var(--amber);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 10px 0 12px;
|
||||||
|
font-size: clamp(2rem, 4vw, 3.2rem);
|
||||||
|
line-height: 1.06;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 1rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
max-width: 76ch;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 24px 0;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li + li {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
color: var(--text);
|
||||||
|
background: oklch(100% 0 0 / 0.06);
|
||||||
|
border: 1px solid oklch(100% 0 0 / 0.08);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.08rem 0.28rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow: auto;
|
||||||
|
background: var(--panel-2);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 14px;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-add {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-del {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-meta {
|
||||||
|
color: var(--faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout {
|
||||||
|
border: 1px solid oklch(78% 0.16 72 / 0.35);
|
||||||
|
background: oklch(78% 0.16 72 / 0.08);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<header>
|
||||||
|
<div class="eyebrow">Turn document • 2026-05-19 18:56 ET</div>
|
||||||
|
<h1>Reconcile PR Conflicts</h1>
|
||||||
|
<p class="summary">
|
||||||
|
Merged <code>forgejo/main</code> into <code>nextjs-upgrade</code>, resolved the checked-in Beads and README conflicts, kept the native deployment work from main, and updated the JetStream tests for the merged nanosecond retention behavior.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>
|
||||||
|
The PR branch now incorporates the current mainline deployment changes while preserving the Next.js upgrade branch. The only hand-edited conflict resolution was in <code>.beads/issues.jsonl</code> and <code>README.md</code>; the rest of the mainline merge applied cleanly.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Resolved <code>.beads/issues.jsonl</code> by keeping issue records from both sides of the merge.</li>
|
||||||
|
<li>Resolved the README deployment workflow section by combining the branch’s command-oriented guidance with main’s newer worker-only, local-server, and native edge cutover notes.</li>
|
||||||
|
<li>Accepted mainline native deployment assets, Docker deployment refinements, API host binding support, deploy timing output, and worker-only deployment scope.</li>
|
||||||
|
<li>Adjusted <code>packages/bus/tests/jetstream.test.ts</code> so retention assertions expect NATS nanoseconds after the merged runtime change.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>
|
||||||
|
The branch was clean before the merge, but Forgejo reported PR conflicts against <code>main</code>. Reproducing the merge locally showed conflicts in the Beads export file and the README deployment section. The automatic merge also brought in mainline native deployment work that touched deploy scripts, Docker deployment files, native systemd templates, public edge documentation, the API host setting, and JetStream retention units.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="tile">
|
||||||
|
<strong>README resolution</strong>
|
||||||
|
<p>Kept Docker as the recommended VPS path, preserved explicit deploy commands, and added <code>--workers-only</code>, local server execution, and native worker iteration guidance.</p>
|
||||||
|
</div>
|
||||||
|
<div class="tile">
|
||||||
|
<strong>Beads resolution</strong>
|
||||||
|
<p>Removed conflict markers without dropping either branch’s issue records, so Beads history remains complete.</p>
|
||||||
|
</div>
|
||||||
|
<div class="tile">
|
||||||
|
<strong>Test repair</strong>
|
||||||
|
<p>Main now stores JetStream <code>max_age</code> in nanoseconds via NATS helpers. Tests now assert against <code>nanos(...)</code> instead of raw millisecond values.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Relevant Diff Snippets</h2>
|
||||||
|
<p>
|
||||||
|
Diff snippets are presented in the style of <a href="https://diffs.com/docs">diffs.com</a>, using structured additions and deletions for quick review.
|
||||||
|
</p>
|
||||||
|
<pre><code><span class="diff-meta">diff --git a/README.md b/README.md</span>
|
||||||
|
<span class="diff-del">- Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, `--fast`, `--no-build`, and `--force-recreate`.</span>
|
||||||
|
<span class="diff-add">+ Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, `--workers-only`, `--fast`, `--no-build`, and `--force-recreate`.</span>
|
||||||
|
<span class="diff-add">+ When run from `/home/delta/islandflow` on the VPS itself, `./deploy` can execute locally instead of SSHing back into the same server.</span>
|
||||||
|
<span class="diff-del">- 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.</span>
|
||||||
|
<span class="diff-add">+ Native deployment expects Bun, systemd units, host-reachable infra, and deliberate reverse-proxy changes. Native deploys are intended primarily for worker-only fast iteration until the public edge is cut over deliberately.</span></code></pre>
|
||||||
|
|
||||||
|
<pre><code><span class="diff-meta">diff --git a/packages/bus/tests/jetstream.test.ts b/packages/bus/tests/jetstream.test.ts</span>
|
||||||
|
<span class="diff-del">- import type { JetStreamManager, StreamConfig } from "nats";</span>
|
||||||
|
<span class="diff-add">+ import { nanos, type JetStreamManager, type StreamConfig } from "nats";</span>
|
||||||
|
<span class="diff-del">- max_age: 3_600_000,</span>
|
||||||
|
<span class="diff-add">+ max_age: nanos(3_600_000),</span>
|
||||||
|
<span class="diff-del">- max_age: 43_200_000,</span>
|
||||||
|
<span class="diff-add">+ max_age: nanos(43_200_000),</span></code></pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<p>
|
||||||
|
The PR should no longer show merge conflicts against main. Users and operators get the Next.js upgrade branch plus the newer deployment safety work from main, including worker-only native deploy guidance and current Docker deployment notes.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>git diff --check</code> passed.</li>
|
||||||
|
<li><code>bun run scripts/deploy.ts --help</code> passed.</li>
|
||||||
|
<li><code>bun run check:docker-workspace</code> passed.</li>
|
||||||
|
<li><code>bun test services/api/tests packages/bus/tests</code> passed with 45 tests.</li>
|
||||||
|
<li><code>bun --cwd=apps/web run build</code> passed on Next.js 16.2.6.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<div class="callout">
|
||||||
|
<p>
|
||||||
|
The first focused test run failed because the merged JetStream implementation correctly returned nanosecond retention values while the existing tests still expected milliseconds. The tests were updated to use the same NATS <code>nanos</code> helper as the runtime behavior, then the suite passed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<ul>
|
||||||
|
<li>No new follow-up was created from this reconciliation.</li>
|
||||||
|
<li>Existing deployment follow-ups remain in Beads, including native public edge posture and cutover decisions.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
229
docs/turns/2026-05-19-upgrade-nextjs-16.html
Normal file
229
docs/turns/2026-05-19-upgrade-nextjs-16.html
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Upgrade apps/web to Next.js 16.2.6</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg: #17111d;
|
||||||
|
--panel: #24172d;
|
||||||
|
--panel-strong: #30203b;
|
||||||
|
--text: #f3eaf8;
|
||||||
|
--muted: #c8b7d4;
|
||||||
|
--accent: #d9a6ff;
|
||||||
|
--accent-2: #ff9fc7;
|
||||||
|
--line: #5d3f70;
|
||||||
|
--code: #130d18;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 20% 0%, rgba(217, 166, 255, 0.18), transparent 32rem),
|
||||||
|
linear-gradient(145deg, #17111d 0%, #1d1424 56%, #140f19 100%);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "IBM Plex Sans", system-ui, sans-serif;
|
||||||
|
line-height: 1.62;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(980px, calc(100% - 32px));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 56px 0 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
margin-bottom: 34px;
|
||||||
|
padding-bottom: 26px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
color: var(--accent-2);
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
letter-spacing: 0;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
max-width: 780px;
|
||||||
|
margin: 0;
|
||||||
|
font-size: clamp(2rem, 5vw, 4.2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 1.18rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-top: 22px;
|
||||||
|
padding: 22px;
|
||||||
|
border: 1px solid rgba(217, 166, 255, 0.22);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(36, 23, 45, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
li {
|
||||||
|
max-width: 74ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-family: "IBM Plex Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
color: #f6c0dd;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 14px 0 0;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid rgba(255, 159, 199, 0.22);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--code);
|
||||||
|
color: #f6eafd;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
font-size: 1.08rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checklist li::marker {
|
||||||
|
color: var(--accent-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #f0b7ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<header>
|
||||||
|
<p class="eyebrow">Turn document · 2026-05-19</p>
|
||||||
|
<h1>Upgrade apps/web to Next.js 16.2.6</h1>
|
||||||
|
<p class="summary">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.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<p>Upgraded <code>apps/web</code> 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.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Changes Made</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Updated <code>apps/web/package.json</code> to request <code>next ^16.2.6</code>, <code>react ^19.2.0</code>, and <code>react-dom ^19.2.0</code>.</li>
|
||||||
|
<li>Updated React type dependencies to <code>@types/react ^19.2.7</code> and added <code>@types/react-dom ^19.2.3</code>.</li>
|
||||||
|
<li>Ran <code>bun install</code>, which resolved Next to <code>16.2.6</code> and React/React DOM to <code>19.2.6</code> in <code>bun.lock</code>.</li>
|
||||||
|
<li>Ran <code>bun run sync:docker-workspace</code> so <code>deployment/docker/workspace-root/bun.lock</code> matches the root lock snapshot.</li>
|
||||||
|
<li>Adjusted the terminal list ref types to accept <code>HTMLDivElement | null</code>, matching React 19's stricter ref object typing.</li>
|
||||||
|
<li>Allowed Next 16 to regenerate <code>apps/web/next-env.d.ts</code> with its updated TypeScript reference comment and generated route type import.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Context</h2>
|
||||||
|
<p>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.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Important Implementation Details</h2>
|
||||||
|
<p>No broad codemod was run. The only source-code change was a targeted type correction in <code>apps/web/app/terminal.tsx</code>. 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.</p>
|
||||||
|
<p>The Docker workspace sync changed the mirrored lockfile, but did not need to rewrite the mirrored package manifest or TypeScript base config.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Relevant Diff Snippets</h2>
|
||||||
|
<pre><code>"next": "^16.2.6",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"@types/react": "^19.2.7",
|
||||||
|
"@types/react-dom": "^19.2.3"</code></pre>
|
||||||
|
<pre><code>type ListScrollState = {
|
||||||
|
listRef: React.RefObject<HTMLDivElement | null>;
|
||||||
|
listNode: HTMLDivElement | null;
|
||||||
|
setListRef: (node: HTMLDivElement | null) => void;
|
||||||
|
};</code></pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Expected Impact for End-Users</h2>
|
||||||
|
<p>There should be no intentional user-facing behavior change. The expected visible behavior remains: <code>/</code>, <code>/tape</code>, and <code>/news</code> render the terminal app; <code>/signals</code>, <code>/charts</code>, and <code>/replay</code> redirect to <code>/</code>; synthetic admin API routes keep their gated proxy behavior.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Validation</h2>
|
||||||
|
<ul class="checklist">
|
||||||
|
<li>Baseline before edits: <code>bun --cwd=apps/web run build</code> passed on Next 14.2.35.</li>
|
||||||
|
<li>Baseline before edits: <code>bun test apps/web/app/routes.test.ts</code> passed, 3 tests.</li>
|
||||||
|
<li>Baseline before edits: <code>bun test apps/web/app/terminal.test.ts</code> passed, 70 tests.</li>
|
||||||
|
<li>Baseline before edits: <code>bun test apps/web/app/api/admin/synthetic/routes.test.ts</code> passed, 4 tests.</li>
|
||||||
|
<li>After upgrade: <code>bun --cwd=apps/web run build</code> passed on Next 16.2.6.</li>
|
||||||
|
<li>After upgrade: <code>bun test apps/web/app/routes.test.ts</code> passed, 3 tests.</li>
|
||||||
|
<li>After upgrade: <code>bun test apps/web/app/terminal.test.ts</code> passed, 70 tests.</li>
|
||||||
|
<li>After upgrade: <code>bun test apps/web/app/api/admin/synthetic/routes.test.ts</code> passed, 4 tests.</li>
|
||||||
|
<li>After upgrade: <code>bun run check:docker-workspace</code> passed.</li>
|
||||||
|
<li>Manual smoke: <code>bun run dev:web</code> served Next 16.2.6 on <code>localhost:3000</code>.</li>
|
||||||
|
<li>Manual smoke: browser checks confirmed <code>/</code>, <code>/tape</code>, and <code>/news</code> render with title <code>Islandflow Terminal</code>.</li>
|
||||||
|
<li>Manual smoke: <code>/signals</code>, <code>/charts</code>, and <code>/replay</code> returned <code>307</code> redirects to <code>/</code>.</li>
|
||||||
|
<li>Manual smoke: synthetic admin status and control routes returned gated <code>404</code> responses when the internal UI flag was off.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Issues, Limitations, and Mitigations</h2>
|
||||||
|
<p>During <code>dev:web</code> 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 <code>bun run dev</code> if needed.</p>
|
||||||
|
<p>The upgrade did not include a full monorepo test run because the acceptance bar was intentionally web-focused.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Follow-up Work</h2>
|
||||||
|
<ul>
|
||||||
|
<li>No required follow-up Beads issue was opened for this upgrade.</li>
|
||||||
|
<li>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.</li>
|
||||||
|
<li>Optional: run the full monorepo <code>bun test</code> suite before a larger release branch merge.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Helpful Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://nextjs.org/docs/app/guides/upgrading/version-16">Next.js 16 upgrade guide</a></li>
|
||||||
|
<li><a href="https://react.dev/blog/2024/12/05/react-19">React 19 release notes</a></li>
|
||||||
|
<li><a href="https://bun.sh/docs/cli/install">Bun install documentation</a></li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
STREAM_EQUITY_QUOTES,
|
STREAM_EQUITY_QUOTES,
|
||||||
STREAM_FLOW_PACKETS,
|
STREAM_FLOW_PACKETS,
|
||||||
STREAM_INFERRED_DARK,
|
STREAM_INFERRED_DARK,
|
||||||
|
STREAM_NEWS,
|
||||||
STREAM_OPTION_NBBO,
|
STREAM_OPTION_NBBO,
|
||||||
STREAM_OPTION_PRINTS,
|
STREAM_OPTION_PRINTS,
|
||||||
STREAM_OPTION_SIGNAL_PRINTS,
|
STREAM_OPTION_SIGNAL_PRINTS,
|
||||||
|
|
@ -19,6 +20,7 @@ import {
|
||||||
SUBJECT_EQUITY_QUOTES,
|
SUBJECT_EQUITY_QUOTES,
|
||||||
SUBJECT_FLOW_PACKETS,
|
SUBJECT_FLOW_PACKETS,
|
||||||
SUBJECT_INFERRED_DARK,
|
SUBJECT_INFERRED_DARK,
|
||||||
|
SUBJECT_NEWS,
|
||||||
SUBJECT_OPTION_NBBO,
|
SUBJECT_OPTION_NBBO,
|
||||||
SUBJECT_OPTION_PRINTS,
|
SUBJECT_OPTION_PRINTS,
|
||||||
SUBJECT_OPTION_SIGNAL_PRINTS,
|
SUBJECT_OPTION_SIGNAL_PRINTS,
|
||||||
|
|
@ -53,7 +55,8 @@ export const STREAM_CATALOG: readonly KnownStreamDefinition[] = [
|
||||||
retentionClass: "derived"
|
retentionClass: "derived"
|
||||||
},
|
},
|
||||||
{ name: STREAM_CLASSIFIER_HITS, subject: SUBJECT_CLASSIFIER_HITS, 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]));
|
const STREAM_CATALOG_BY_NAME = new Map(STREAM_CATALOG.map((definition) => [definition.name, definition]));
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,5 @@ export const STREAM_CLASSIFIER_HITS = "CLASSIFIER_HITS";
|
||||||
export const SUBJECT_CLASSIFIER_HITS = "flow.classifier_hits";
|
export const SUBJECT_CLASSIFIER_HITS = "flow.classifier_hits";
|
||||||
export const STREAM_ALERTS = "ALERTS";
|
export const STREAM_ALERTS = "ALERTS";
|
||||||
export const SUBJECT_ALERTS = "flow.alerts";
|
export const SUBJECT_ALERTS = "flow.alerts";
|
||||||
|
export const STREAM_NEWS = "NEWS";
|
||||||
|
export const SUBJECT_NEWS = "flow.news";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, expect, it } from "bun:test";
|
import { describe, expect, it } from "bun:test";
|
||||||
import type { JetStreamManager, StreamConfig } from "nats";
|
import { nanos, type JetStreamManager, type StreamConfig } from "nats";
|
||||||
import {
|
import {
|
||||||
auditStreamConfig,
|
auditStreamConfig,
|
||||||
buildKnownStreamConfig,
|
buildKnownStreamConfig,
|
||||||
|
|
@ -52,14 +52,14 @@ const buildAllKnownConfigs = (env: Record<string, string | undefined> = {}) => {
|
||||||
describe("jetstream retention defaults", () => {
|
describe("jetstream retention defaults", () => {
|
||||||
it("resolves raw defaults to 60m and 512 MiB", () => {
|
it("resolves raw defaults to 60m and 512 MiB", () => {
|
||||||
expect(resolveStreamRetention("raw")).toEqual({
|
expect(resolveStreamRetention("raw")).toEqual({
|
||||||
max_age: 3_600_000,
|
max_age: nanos(3_600_000),
|
||||||
max_bytes: 536_870_912
|
max_bytes: 536_870_912
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolves derived defaults to 12h and 256 MiB", () => {
|
it("resolves derived defaults to 12h and 256 MiB", () => {
|
||||||
expect(resolveStreamRetention("derived")).toEqual({
|
expect(resolveStreamRetention("derived")).toEqual({
|
||||||
max_age: 43_200_000,
|
max_age: nanos(43_200_000),
|
||||||
max_bytes: 268_435_456
|
max_bytes: 268_435_456
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -71,7 +71,7 @@ describe("jetstream retention defaults", () => {
|
||||||
STREAM_RAW_MAX_BYTES: "5678"
|
STREAM_RAW_MAX_BYTES: "5678"
|
||||||
})
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
max_age: 1234,
|
max_age: nanos(1234),
|
||||||
max_bytes: 5678
|
max_bytes: 5678
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
EquityPrintJoinSchema,
|
EquityPrintJoinSchema,
|
||||||
InferredDarkEventSchema,
|
InferredDarkEventSchema,
|
||||||
FlowPacketSchema,
|
FlowPacketSchema,
|
||||||
|
NewsStorySchema,
|
||||||
OptionNBBOSchema,
|
OptionNBBOSchema,
|
||||||
OptionPrintSchema,
|
OptionPrintSchema,
|
||||||
SmartMoneyEventSchema
|
SmartMoneyEventSchema
|
||||||
|
|
@ -20,6 +21,7 @@ import type {
|
||||||
EquityPrintJoin,
|
EquityPrintJoin,
|
||||||
InferredDarkEvent,
|
InferredDarkEvent,
|
||||||
FlowPacket,
|
FlowPacket,
|
||||||
|
NewsStory,
|
||||||
SmartMoneyEvent,
|
SmartMoneyEvent,
|
||||||
OptionNBBO,
|
OptionNBBO,
|
||||||
OptionPrint,
|
OptionPrint,
|
||||||
|
|
@ -91,6 +93,13 @@ import {
|
||||||
toSmartMoneyEventRecord,
|
toSmartMoneyEventRecord,
|
||||||
type SmartMoneyEventRecord
|
type SmartMoneyEventRecord
|
||||||
} from "./smart-money-events";
|
} from "./smart-money-events";
|
||||||
|
import {
|
||||||
|
NEWS_TABLE,
|
||||||
|
newsTableDDL,
|
||||||
|
fromNewsRecord,
|
||||||
|
toNewsRecord,
|
||||||
|
type NewsRecord
|
||||||
|
} from "./news";
|
||||||
|
|
||||||
export type ClickHouseOptions = {
|
export type ClickHouseOptions = {
|
||||||
url: string;
|
url: string;
|
||||||
|
|
@ -320,6 +329,12 @@ export const ensureAlertsTable = async (client: ClickHouseClient): Promise<void>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ensureNewsTable = async (client: ClickHouseClient): Promise<void> => {
|
||||||
|
await client.exec({
|
||||||
|
query: newsTableDDL()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const insertOptionPrint = async (
|
export const insertOptionPrint = async (
|
||||||
client: ClickHouseClient,
|
client: ClickHouseClient,
|
||||||
print: OptionPrint
|
print: OptionPrint
|
||||||
|
|
@ -449,6 +464,15 @@ export const insertAlert = async (client: ClickHouseClient, alert: AlertEvent):
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const insertNewsStory = async (client: ClickHouseClient, story: NewsStory): Promise<void> => {
|
||||||
|
const record = toNewsRecord(story);
|
||||||
|
await client.insert({
|
||||||
|
table: NEWS_TABLE,
|
||||||
|
values: [record],
|
||||||
|
format: "JSONEachRow"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export type ClickHouseBatchWriterOptions = {
|
export type ClickHouseBatchWriterOptions = {
|
||||||
flushIntervalMs?: number;
|
flushIntervalMs?: number;
|
||||||
maxRows?: number;
|
maxRows?: number;
|
||||||
|
|
@ -600,6 +624,13 @@ export const enqueueAlertInsert = (
|
||||||
writer.enqueue(ALERTS_TABLE, toAlertRecord(alert));
|
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 => {
|
const clampLimit = (limit: number): number => {
|
||||||
if (!Number.isFinite(limit)) {
|
if (!Number.isFinite(limit)) {
|
||||||
return 100;
|
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<string, unknown>;
|
||||||
|
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 (
|
export const fetchRecentOptionPrints = async (
|
||||||
client: ClickHouseClient,
|
client: ClickHouseClient,
|
||||||
limit: number,
|
limit: number,
|
||||||
|
|
@ -1207,6 +1264,50 @@ export const fetchRecentAlerts = async (
|
||||||
return AlertEventSchema.array().parse(alerts);
|
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<NewsStory[]> => {
|
||||||
|
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<unknown[]>();
|
||||||
|
const records = rows
|
||||||
|
.map(normalizeNewsRow)
|
||||||
|
.filter((record): record is NewsRecord => record !== null);
|
||||||
|
return NewsStorySchema.array().parse(records.map(fromNewsRecord));
|
||||||
|
};
|
||||||
|
|
||||||
const normalizeAlertEvidenceRefs = (refs: string[]): string[] => {
|
const normalizeAlertEvidenceRefs = (refs: string[]): string[] => {
|
||||||
return Array.from(new Set(refs.map((ref) => ref.trim()).filter(Boolean)));
|
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);
|
return AlertEventSchema.array().parse(alerts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchNewsAfter = async (
|
||||||
|
client: ClickHouseClient,
|
||||||
|
afterTs: number,
|
||||||
|
afterSeq: number,
|
||||||
|
limit: number
|
||||||
|
): Promise<NewsStory[]> => {
|
||||||
|
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<unknown[]>();
|
||||||
|
const records = rows
|
||||||
|
.map(normalizeNewsRow)
|
||||||
|
.filter((record): record is NewsRecord => record !== null);
|
||||||
|
return NewsStorySchema.array().parse(records.map(fromNewsRecord));
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchOptionPrintsBefore = async (
|
export const fetchOptionPrintsBefore = async (
|
||||||
client: ClickHouseClient,
|
client: ClickHouseClient,
|
||||||
beforeTs: number,
|
beforeTs: number,
|
||||||
|
|
@ -1778,6 +1900,25 @@ export const fetchAlertsBefore = async (
|
||||||
return AlertEventSchema.array().parse(records.map(fromAlertRecord));
|
return AlertEventSchema.array().parse(records.map(fromAlertRecord));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchNewsBefore = async (
|
||||||
|
client: ClickHouseClient,
|
||||||
|
beforeTs: number,
|
||||||
|
beforeSeq: number,
|
||||||
|
limit: number
|
||||||
|
): Promise<NewsStory[]> => {
|
||||||
|
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<unknown[]>();
|
||||||
|
const records = rows
|
||||||
|
.map(normalizeNewsRow)
|
||||||
|
.filter((record): record is NewsRecord => record !== null);
|
||||||
|
return NewsStorySchema.array().parse(records.map(fromNewsRecord));
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchInferredDarkBefore = async (
|
export const fetchInferredDarkBefore = async (
|
||||||
client: ClickHouseClient,
|
client: ClickHouseClient,
|
||||||
beforeTs: number,
|
beforeTs: number,
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ export * from "./equity-print-joins";
|
||||||
export * from "./inferred-dark";
|
export * from "./inferred-dark";
|
||||||
export * from "./option-prints";
|
export * from "./option-prints";
|
||||||
export * from "./option-nbbo";
|
export * from "./option-nbbo";
|
||||||
|
export * from "./news";
|
||||||
|
|
|
||||||
102
packages/storage/src/news.ts
Normal file
102
packages/storage/src/news.ts
Normal file
|
|
@ -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
|
||||||
|
};
|
||||||
|
};
|
||||||
78
packages/storage/tests/news.test.ts
Normal file
78
packages/storage/tests/news.test.ts
Normal file
|
|
@ -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<T>() {
|
||||||
|
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: "<p>TSLA rises</p>",
|
||||||
|
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)");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -262,3 +262,26 @@ export const InferredDarkEventSchema = EventMetaSchema.merge(
|
||||||
);
|
);
|
||||||
|
|
||||||
export type InferredDarkEvent = z.infer<typeof InferredDarkEventSchema>;
|
export type InferredDarkEvent = z.infer<typeof InferredDarkEventSchema>;
|
||||||
|
|
||||||
|
export const NewsSymbolResolutionSchema = z.enum(["provider", "derived", "mixed", "none"]);
|
||||||
|
|
||||||
|
export type NewsSymbolResolution = z.infer<typeof NewsSymbolResolutionSchema>;
|
||||||
|
|
||||||
|
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<typeof NewsStorySchema>;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
EquityQuoteSchema,
|
EquityQuoteSchema,
|
||||||
FlowPacketSchema,
|
FlowPacketSchema,
|
||||||
InferredDarkEventSchema,
|
InferredDarkEventSchema,
|
||||||
|
NewsStorySchema,
|
||||||
OptionNBBOSchema,
|
OptionNBBOSchema,
|
||||||
OptionPrintSchema,
|
OptionPrintSchema,
|
||||||
SmartMoneyEventSchema
|
SmartMoneyEventSchema
|
||||||
|
|
@ -34,7 +35,8 @@ export const LiveGenericChannelSchema = z.enum([
|
||||||
"smart-money",
|
"smart-money",
|
||||||
"classifier-hits",
|
"classifier-hits",
|
||||||
"alerts",
|
"alerts",
|
||||||
"inferred-dark"
|
"inferred-dark",
|
||||||
|
"news"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const LiveChannelSchema = z.enum([
|
export const LiveChannelSchema = z.enum([
|
||||||
|
|
@ -48,6 +50,7 @@ export const LiveChannelSchema = z.enum([
|
||||||
"classifier-hits",
|
"classifier-hits",
|
||||||
"alerts",
|
"alerts",
|
||||||
"inferred-dark",
|
"inferred-dark",
|
||||||
|
"news",
|
||||||
"equity-candles",
|
"equity-candles",
|
||||||
"equity-overlay"
|
"equity-overlay"
|
||||||
]);
|
]);
|
||||||
|
|
@ -91,7 +94,7 @@ export const LiveSubscriptionSchema = z.discriminatedUnion("channel", [
|
||||||
snapshot_limit: z.number().int().positive().optional()
|
snapshot_limit: z.number().int().positive().optional()
|
||||||
}),
|
}),
|
||||||
z.object({
|
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()
|
snapshot_limit: z.number().int().positive().optional()
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
|
|
@ -123,6 +126,7 @@ const livePayloadSchemas = {
|
||||||
"classifier-hits": ClassifierHitEventSchema,
|
"classifier-hits": ClassifierHitEventSchema,
|
||||||
alerts: AlertEventSchema,
|
alerts: AlertEventSchema,
|
||||||
"inferred-dark": InferredDarkEventSchema,
|
"inferred-dark": InferredDarkEventSchema,
|
||||||
|
news: NewsStorySchema,
|
||||||
"equity-candles": EquityCandleSchema,
|
"equity-candles": EquityCandleSchema,
|
||||||
"equity-overlay": EquityPrintSchema
|
"equity-overlay": EquityPrintSchema
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
describe("live protocol types", () => {
|
describe("live protocol types", () => {
|
||||||
it("builds stable keys for generic and parameterized subscriptions", () => {
|
it("builds stable keys for generic and parameterized subscriptions", () => {
|
||||||
expect(getSubscriptionKey({ channel: "flow" })).toBe("flow|{}");
|
expect(getSubscriptionKey({ channel: "flow" })).toBe("flow|{}");
|
||||||
|
expect(getSubscriptionKey({ channel: "news" })).toBe("news");
|
||||||
expect(
|
expect(
|
||||||
getSubscriptionKey({
|
getSubscriptionKey({
|
||||||
channel: "options",
|
channel: "options",
|
||||||
|
|
@ -53,12 +54,13 @@ describe("live protocol types", () => {
|
||||||
op: "subscribe",
|
op: "subscribe",
|
||||||
subscriptions: [
|
subscriptions: [
|
||||||
{ channel: "flow", filters: { nbboSides: ["AA", "A"], minNotional: 50000 } },
|
{ channel: "flow", filters: { nbboSides: ["AA", "A"], minNotional: 50000 } },
|
||||||
|
{ channel: "news", snapshot_limit: 100 },
|
||||||
{ channel: "equity-candles", underlying_id: "SPY", interval_ms: 60000 }
|
{ channel: "equity-candles", underlying_id: "SPY", interval_ms: 60000 }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(parsed.op).toBe("subscribe");
|
expect(parsed.op).toBe("subscribe");
|
||||||
expect(parsed.subscriptions).toHaveLength(2);
|
expect(parsed.subscriptions).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("validates snapshot and event server messages", () => {
|
it("validates snapshot and event server messages", () => {
|
||||||
|
|
@ -74,18 +76,24 @@ describe("live protocol types", () => {
|
||||||
});
|
});
|
||||||
const event = LiveServerMessageSchema.parse({
|
const event = LiveServerMessageSchema.parse({
|
||||||
op: "event",
|
op: "event",
|
||||||
subscription: { channel: "equity-overlay", underlying_id: "SPY" },
|
subscription: { channel: "news" },
|
||||||
item: {
|
item: {
|
||||||
source_ts: 100,
|
source_ts: 100,
|
||||||
ingest_ts: 101,
|
ingest_ts: 101,
|
||||||
seq: 1,
|
seq: 1,
|
||||||
trace_id: "eq-1",
|
trace_id: "alpaca:1",
|
||||||
ts: 100,
|
story_id: 1,
|
||||||
underlying_id: "SPY",
|
provider: "alpaca",
|
||||||
price: 500,
|
source: "Benzinga",
|
||||||
size: 10,
|
headline: "TSLA rises",
|
||||||
exchange: "X",
|
summary: "",
|
||||||
offExchangeFlag: true
|
content_html: "<p>TSLA rises</p>",
|
||||||
|
url: "https://example.com/story",
|
||||||
|
published_ts: 100,
|
||||||
|
updated_ts: 100,
|
||||||
|
provider_symbols: ["TSLA"],
|
||||||
|
resolved_symbols: ["TSLA"],
|
||||||
|
symbol_resolution: "provider"
|
||||||
},
|
},
|
||||||
watermark: cursor
|
watermark: cursor
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@ const NATIVE_UNITS = {
|
||||||
ingestOptions:
|
ingestOptions:
|
||||||
process.env.DEPLOY_NATIVE_INGEST_OPTIONS_UNIT?.trim() || "islandflow-ingest-options",
|
process.env.DEPLOY_NATIVE_INGEST_OPTIONS_UNIT?.trim() || "islandflow-ingest-options",
|
||||||
ingestEquities:
|
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;
|
} as const;
|
||||||
const DOCKER_CORE_SERVICES = [
|
const DOCKER_CORE_SERVICES = [
|
||||||
"api",
|
"api",
|
||||||
|
|
@ -65,14 +66,16 @@ const DOCKER_CORE_SERVICES = [
|
||||||
"compute",
|
"compute",
|
||||||
"candles",
|
"candles",
|
||||||
"ingest-options",
|
"ingest-options",
|
||||||
"ingest-equities"
|
"ingest-equities",
|
||||||
|
"ingest-news"
|
||||||
] as const;
|
] as const;
|
||||||
const DOCKER_BACKEND_SERVICES = [
|
const DOCKER_BACKEND_SERVICES = [
|
||||||
"api",
|
"api",
|
||||||
"compute",
|
"compute",
|
||||||
"candles",
|
"candles",
|
||||||
"ingest-options",
|
"ingest-options",
|
||||||
"ingest-equities"
|
"ingest-equities",
|
||||||
|
"ingest-news"
|
||||||
] as const;
|
] as const;
|
||||||
const DOCKER_WORKER_SERVICES = [
|
const DOCKER_WORKER_SERVICES = [
|
||||||
"compute",
|
"compute",
|
||||||
|
|
@ -126,7 +129,8 @@ Environment:
|
||||||
DEPLOY_NATIVE_COMPUTE_UNIT Override native compute systemd unit name.
|
DEPLOY_NATIVE_COMPUTE_UNIT Override native compute systemd unit name.
|
||||||
DEPLOY_NATIVE_CANDLES_UNIT Override native candles systemd unit name.
|
DEPLOY_NATIVE_CANDLES_UNIT Override native candles systemd unit name.
|
||||||
DEPLOY_NATIVE_INGEST_OPTIONS_UNIT Override native ingest-options 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);
|
process.exit(exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -547,7 +551,8 @@ function nativeUnitsForScope(scope: DeployScope): string[] {
|
||||||
NATIVE_UNITS.compute,
|
NATIVE_UNITS.compute,
|
||||||
NATIVE_UNITS.candles,
|
NATIVE_UNITS.candles,
|
||||||
NATIVE_UNITS.ingestOptions,
|
NATIVE_UNITS.ingestOptions,
|
||||||
NATIVE_UNITS.ingestEquities
|
NATIVE_UNITS.ingestEquities,
|
||||||
|
NATIVE_UNITS.ingestNews
|
||||||
];
|
];
|
||||||
case "workers":
|
case "workers":
|
||||||
return [
|
return [
|
||||||
|
|
@ -563,7 +568,8 @@ function nativeUnitsForScope(scope: DeployScope): string[] {
|
||||||
NATIVE_UNITS.compute,
|
NATIVE_UNITS.compute,
|
||||||
NATIVE_UNITS.candles,
|
NATIVE_UNITS.candles,
|
||||||
NATIVE_UNITS.ingestOptions,
|
NATIVE_UNITS.ingestOptions,
|
||||||
NATIVE_UNITS.ingestEquities
|
NATIVE_UNITS.ingestEquities,
|
||||||
|
NATIVE_UNITS.ingestNews
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,7 @@ process.on("SIGHUP", () => handleSignal("SIGHUP"));
|
||||||
const tasks: ChildSpec[] = [
|
const tasks: ChildSpec[] = [
|
||||||
{ name: "ingest-options", cmd: ["bun", "run", "dev"], cwd: "services/ingest-options" },
|
{ name: "ingest-options", cmd: ["bun", "run", "dev"], cwd: "services/ingest-options" },
|
||||||
{ name: "ingest-equities", cmd: ["bun", "run", "dev"], cwd: "services/ingest-equities" },
|
{ 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: "compute", cmd: ["bun", "run", "dev"], cwd: "services/compute" },
|
||||||
{ name: "candles", cmd: ["bun", "run", "dev"], cwd: "services/candles" },
|
{ name: "candles", cmd: ["bun", "run", "dev"], cwd: "services/candles" },
|
||||||
{ name: "refdata", cmd: ["bun", "run", "dev"], cwd: "services/refdata" },
|
{ name: "refdata", cmd: ["bun", "run", "dev"], cwd: "services/refdata" },
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,7 @@ const serviceTasks: ChildSpec[] = [
|
||||||
{ name: "web", cmd: ["bun", "run", "dev"], cwd: "apps/web" },
|
{ name: "web", cmd: ["bun", "run", "dev"], cwd: "apps/web" },
|
||||||
{ name: "ingest-options", cmd: ["bun", "run", "dev"], cwd: "services/ingest-options" },
|
{ name: "ingest-options", cmd: ["bun", "run", "dev"], cwd: "services/ingest-options" },
|
||||||
{ name: "ingest-equities", cmd: ["bun", "run", "dev"], cwd: "services/ingest-equities" },
|
{ 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: "compute", cmd: ["bun", "run", "dev"], cwd: "services/compute" },
|
||||||
{ name: "candles", cmd: ["bun", "run", "dev"], cwd: "services/candles" },
|
{ name: "candles", cmd: ["bun", "run", "dev"], cwd: "services/candles" },
|
||||||
{ name: "refdata", cmd: ["bun", "run", "dev"], cwd: "services/refdata" },
|
{ name: "refdata", cmd: ["bun", "run", "dev"], cwd: "services/refdata" },
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
SUBJECT_EQUITY_QUOTES,
|
SUBJECT_EQUITY_QUOTES,
|
||||||
SUBJECT_INFERRED_DARK,
|
SUBJECT_INFERRED_DARK,
|
||||||
SUBJECT_FLOW_PACKETS,
|
SUBJECT_FLOW_PACKETS,
|
||||||
|
SUBJECT_NEWS,
|
||||||
SUBJECT_SMART_MONEY_EVENTS,
|
SUBJECT_SMART_MONEY_EVENTS,
|
||||||
SUBJECT_OPTION_NBBO,
|
SUBJECT_OPTION_NBBO,
|
||||||
SUBJECT_OPTION_SIGNAL_PRINTS,
|
SUBJECT_OPTION_SIGNAL_PRINTS,
|
||||||
|
|
@ -20,6 +21,7 @@ import {
|
||||||
STREAM_EQUITY_QUOTES,
|
STREAM_EQUITY_QUOTES,
|
||||||
STREAM_INFERRED_DARK,
|
STREAM_INFERRED_DARK,
|
||||||
STREAM_FLOW_PACKETS,
|
STREAM_FLOW_PACKETS,
|
||||||
|
STREAM_NEWS,
|
||||||
STREAM_SMART_MONEY_EVENTS,
|
STREAM_SMART_MONEY_EVENTS,
|
||||||
STREAM_OPTION_NBBO,
|
STREAM_OPTION_NBBO,
|
||||||
STREAM_OPTION_SIGNAL_PRINTS,
|
STREAM_OPTION_SIGNAL_PRINTS,
|
||||||
|
|
@ -35,6 +37,7 @@ import {
|
||||||
import {
|
import {
|
||||||
createClickHouseClient,
|
createClickHouseClient,
|
||||||
ensureAlertsTable,
|
ensureAlertsTable,
|
||||||
|
ensureNewsTable,
|
||||||
ensureClassifierHitsTable,
|
ensureClassifierHitsTable,
|
||||||
ensureEquityCandlesTable,
|
ensureEquityCandlesTable,
|
||||||
ensureEquityPrintJoinsTable,
|
ensureEquityPrintJoinsTable,
|
||||||
|
|
@ -48,6 +51,8 @@ import {
|
||||||
fetchAlertsAfter,
|
fetchAlertsAfter,
|
||||||
fetchAlertsBefore,
|
fetchAlertsBefore,
|
||||||
fetchAlertContextByTraceId,
|
fetchAlertContextByTraceId,
|
||||||
|
fetchNewsAfter,
|
||||||
|
fetchNewsBefore,
|
||||||
fetchClassifierHitsAfter,
|
fetchClassifierHitsAfter,
|
||||||
fetchClassifierHitsBefore,
|
fetchClassifierHitsBefore,
|
||||||
fetchSmartMoneyEventsAfter,
|
fetchSmartMoneyEventsAfter,
|
||||||
|
|
@ -58,6 +63,7 @@ import {
|
||||||
fetchFlowPacketsByMemberTraceIds,
|
fetchFlowPacketsByMemberTraceIds,
|
||||||
fetchFlowPacketsBefore,
|
fetchFlowPacketsBefore,
|
||||||
fetchRecentAlerts,
|
fetchRecentAlerts,
|
||||||
|
fetchRecentNews,
|
||||||
fetchRecentClassifierHits,
|
fetchRecentClassifierHits,
|
||||||
fetchRecentSmartMoneyEvents,
|
fetchRecentSmartMoneyEvents,
|
||||||
fetchRecentEquityPrintJoins,
|
fetchRecentEquityPrintJoins,
|
||||||
|
|
@ -99,6 +105,7 @@ import {
|
||||||
EquityQuoteSchema,
|
EquityQuoteSchema,
|
||||||
FeedSnapshot,
|
FeedSnapshot,
|
||||||
InferredDarkEventSchema,
|
InferredDarkEventSchema,
|
||||||
|
NewsStorySchema,
|
||||||
LiveClientMessageSchema,
|
LiveClientMessageSchema,
|
||||||
LiveServerMessage,
|
LiveServerMessage,
|
||||||
LiveSubscription,
|
LiveSubscription,
|
||||||
|
|
@ -677,7 +684,8 @@ const run = async () => {
|
||||||
STREAM_FLOW_PACKETS,
|
STREAM_FLOW_PACKETS,
|
||||||
STREAM_SMART_MONEY_EVENTS,
|
STREAM_SMART_MONEY_EVENTS,
|
||||||
STREAM_CLASSIFIER_HITS,
|
STREAM_CLASSIFIER_HITS,
|
||||||
STREAM_ALERTS
|
STREAM_ALERTS,
|
||||||
|
STREAM_NEWS
|
||||||
],
|
],
|
||||||
{ logger }
|
{ logger }
|
||||||
);
|
);
|
||||||
|
|
@ -720,6 +728,7 @@ const run = async () => {
|
||||||
await ensureSmartMoneyEventsTable(clickhouse);
|
await ensureSmartMoneyEventsTable(clickhouse);
|
||||||
await ensureClassifierHitsTable(clickhouse);
|
await ensureClassifierHitsTable(clickhouse);
|
||||||
await ensureAlertsTable(clickhouse);
|
await ensureAlertsTable(clickhouse);
|
||||||
|
await ensureNewsTable(clickhouse);
|
||||||
});
|
});
|
||||||
|
|
||||||
let redis: ReturnType<typeof createClient> | null = null;
|
let redis: ReturnType<typeof createClient> | null = null;
|
||||||
|
|
@ -844,6 +853,11 @@ const run = async () => {
|
||||||
subject: SUBJECT_ALERTS,
|
subject: SUBJECT_ALERTS,
|
||||||
stream: STREAM_ALERTS,
|
stream: STREAM_ALERTS,
|
||||||
durableName: "api-alerts"
|
durableName: "api-alerts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: SUBJECT_NEWS,
|
||||||
|
stream: STREAM_NEWS,
|
||||||
|
durableName: "api-news"
|
||||||
}
|
}
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|
@ -992,10 +1006,16 @@ const run = async () => {
|
||||||
consumerBindings[10].durableName
|
consumerBindings[10].durableName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const newsSubscription = await subscribeWithReset(
|
||||||
|
consumerBindings[11].subject,
|
||||||
|
consumerBindings[11].stream,
|
||||||
|
consumerBindings[11].durableName
|
||||||
|
);
|
||||||
|
|
||||||
const fanoutLive = async (
|
const fanoutLive = async (
|
||||||
subscription: LiveSubscription,
|
subscription: LiveSubscription,
|
||||||
item: unknown,
|
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);
|
const watermark = await liveState.ingest(ingestChannel, item);
|
||||||
|
|
||||||
|
|
@ -1253,6 +1273,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 pumpOptions();
|
||||||
void pumpOptionNbbo();
|
void pumpOptionNbbo();
|
||||||
void pumpEquities();
|
void pumpEquities();
|
||||||
|
|
@ -1264,6 +1299,7 @@ const run = async () => {
|
||||||
void pumpSmartMoney();
|
void pumpSmartMoney();
|
||||||
void pumpClassifierHits();
|
void pumpClassifierHits();
|
||||||
void pumpAlerts();
|
void pumpAlerts();
|
||||||
|
void pumpNews();
|
||||||
|
|
||||||
const buildSyntheticStatusBody = () => {
|
const buildSyntheticStatusBody = () => {
|
||||||
const derived =
|
const derived =
|
||||||
|
|
@ -1492,6 +1528,12 @@ const run = async () => {
|
||||||
return jsonResponse({ data });
|
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)) {
|
if (req.method === "GET" && isAlertContextPath(url.pathname)) {
|
||||||
try {
|
try {
|
||||||
const traceId = parseAlertContextTraceIdPath(url.pathname);
|
const traceId = parseAlertContextTraceIdPath(url.pathname);
|
||||||
|
|
@ -1609,6 +1651,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)) {
|
if (req.method === "GET" && /^\/flow\/packets\/[^/]+$/.test(url.pathname)) {
|
||||||
const id = decodeURIComponent(url.pathname.slice("/flow/packets/".length));
|
const id = decodeURIComponent(url.pathname.slice("/flow/packets/".length));
|
||||||
const data = await fetchFlowPacketById(clickhouse, id);
|
const data = await fetchFlowPacketById(clickhouse, id);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
fetchRecentEquityQuotes,
|
fetchRecentEquityQuotes,
|
||||||
fetchRecentFlowPackets,
|
fetchRecentFlowPackets,
|
||||||
fetchRecentInferredDark,
|
fetchRecentInferredDark,
|
||||||
|
fetchRecentNews,
|
||||||
fetchRecentOptionNBBO,
|
fetchRecentOptionNBBO,
|
||||||
fetchRecentSmartMoneyEvents,
|
fetchRecentSmartMoneyEvents,
|
||||||
type ClickHouseClient
|
type ClickHouseClient
|
||||||
|
|
@ -25,6 +26,7 @@ import {
|
||||||
FeedSnapshot,
|
FeedSnapshot,
|
||||||
FlowPacketSchema,
|
FlowPacketSchema,
|
||||||
InferredDarkEventSchema,
|
InferredDarkEventSchema,
|
||||||
|
NewsStorySchema,
|
||||||
LiveChannelHealth,
|
LiveChannelHealth,
|
||||||
LiveGenericChannel,
|
LiveGenericChannel,
|
||||||
LiveHotChannel,
|
LiveHotChannel,
|
||||||
|
|
@ -40,6 +42,7 @@ import {
|
||||||
type EquityCandle,
|
type EquityCandle,
|
||||||
type EquityPrint,
|
type EquityPrint,
|
||||||
type LiveChannel,
|
type LiveChannel,
|
||||||
|
type NewsStory,
|
||||||
type OptionPrint
|
type OptionPrint
|
||||||
} from "@islandflow/types";
|
} from "@islandflow/types";
|
||||||
import { createMetrics } from "@islandflow/observability";
|
import { createMetrics } from "@islandflow/observability";
|
||||||
|
|
@ -63,7 +66,8 @@ const GENERIC_LIMIT_ENV_KEYS: Record<LiveGenericChannel, string> = {
|
||||||
"smart-money": "LIVE_LIMIT_SMART_MONEY",
|
"smart-money": "LIVE_LIMIT_SMART_MONEY",
|
||||||
"classifier-hits": "LIVE_LIMIT_CLASSIFIER_HITS",
|
"classifier-hits": "LIVE_LIMIT_CLASSIFIER_HITS",
|
||||||
alerts: "LIVE_LIMIT_ALERTS",
|
alerts: "LIVE_LIMIT_ALERTS",
|
||||||
"inferred-dark": "LIVE_LIMIT_INFERRED_DARK"
|
"inferred-dark": "LIVE_LIMIT_INFERRED_DARK",
|
||||||
|
news: "LIVE_LIMIT_NEWS"
|
||||||
};
|
};
|
||||||
|
|
||||||
const CHART_LIMITS = {
|
const CHART_LIMITS = {
|
||||||
|
|
@ -81,7 +85,8 @@ const DEFAULT_LIVE_LIMITS: GenericLiveLimits = {
|
||||||
"smart-money": 300,
|
"smart-money": 300,
|
||||||
"classifier-hits": 300,
|
"classifier-hits": 300,
|
||||||
alerts: 300,
|
alerts: 300,
|
||||||
"inferred-dark": 300
|
"inferred-dark": 300,
|
||||||
|
news: 100
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_SCOPED_CACHE_MAX_KEYS = 32;
|
const DEFAULT_SCOPED_CACHE_MAX_KEYS = 32;
|
||||||
|
|
@ -196,16 +201,28 @@ export const resolveGenericLiveLimits = (env: NodeJS.ProcessEnv = process.env):
|
||||||
env,
|
env,
|
||||||
"inferred-dark",
|
"inferred-dark",
|
||||||
env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS["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 extractFreshnessTs = (channel: LiveGenericChannel, item: any): number | null => {
|
||||||
const parsed = Number(value);
|
switch (channel) {
|
||||||
if (!Number.isFinite(parsed)) {
|
case "options":
|
||||||
return fallback;
|
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 => ({
|
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)
|
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<
|
type RedisLike = Pick<
|
||||||
RedisClientType,
|
RedisClientType,
|
||||||
|
|
@ -318,6 +342,14 @@ const getGenericConfig = (limits: GenericLiveLimits): {
|
||||||
parse: (value) => InferredDarkEventSchema.parse(value),
|
parse: (value) => InferredDarkEventSchema.parse(value),
|
||||||
cursor: (item) => ({ ts: item.source_ts, seq: item.seq }),
|
cursor: (item) => ({ ts: item.source_ts, seq: item.seq }),
|
||||||
fetchRecent: fetchRecentInferredDark
|
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 = <T>(
|
||||||
return sortGenericItems(items, config.cursor).slice(0, config.limit);
|
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 = (
|
const isWithinLiveFeedLookback = (
|
||||||
channel: LiveGenericChannel,
|
channel: LiveGenericChannel,
|
||||||
item: unknown,
|
item: unknown,
|
||||||
|
|
|
||||||
16
services/ingest-news/package.json
Normal file
16
services/ingest-news/package.json
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
216
services/ingest-news/src/index.ts
Normal file
216
services/ingest-news/src/index.ts
Normal file
|
|
@ -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<string, string> => ({
|
||||||
|
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<AlpacaNewsItem[]> => {
|
||||||
|
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<string, unknown>;
|
||||||
|
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);
|
||||||
|
});
|
||||||
70
services/ingest-news/src/symbols.ts
Normal file
70
services/ingest-news/src/symbols.ts
Normal file
|
|
@ -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<string>();
|
||||||
|
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"
|
||||||
|
};
|
||||||
|
};
|
||||||
30
services/ingest-news/tests/symbols.test.ts
Normal file
30
services/ingest-news/tests/symbols.test.ts
Normal file
|
|
@ -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"], "<p>No extra tickers here.</p>");
|
||||||
|
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([], '<a href="/quote/TSLA">TSLA</a>');
|
||||||
|
expect(result.resolved_symbols).toEqual(["TSLA"]);
|
||||||
|
expect(result.symbol_resolution).toBe("derived");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to exchange and dollar patterns", () => {
|
||||||
|
const result = resolveNewsSymbols([], "<p>NASDAQ:TSLA met with $IBM executives.</p>");
|
||||||
|
expect(result.resolved_symbols).toEqual(["TSLA", "IBM"]);
|
||||||
|
expect(result.symbol_resolution).toBe("derived");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("dedupes and uppercases merged symbols", () => {
|
||||||
|
const result = resolveNewsSymbols(["tsla"], "<p>$TSLA and NASDAQ:TSLA</p>");
|
||||||
|
expect(result.provider_symbols).toEqual(["TSLA"]);
|
||||||
|
expect(result.resolved_symbols).toEqual(["TSLA"]);
|
||||||
|
expect(result.symbol_resolution).toBe("mixed");
|
||||||
|
});
|
||||||
|
});
|
||||||
7
services/ingest-news/tsconfig.json
Normal file
7
services/ingest-news/tsconfig.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue