From e54fa4b2deb14e9261b3270722dd84a20b02982f Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 22 May 2026 21:39:55 -0400
Subject: [PATCH 01/45] cap live api caches and cut redis churn
---
.beads/issues.jsonl | 1 +
.env.example | 3 +-
apps/web/app/terminal.tsx | 4 +-
deployment/docker/.env.example | 3 +-
services/api/src/index.ts | 58 ++++++++
services/api/src/live.ts | 236 +++++++++++++++++++++++++++-----
services/api/tests/live.test.ts | 40 +++++-
7 files changed, 304 insertions(+), 41 deletions(-)
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 36cf3df..c6b5525 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,3 +1,4 @@
+{"_type":"issue","id":"islandflow-thp","title":"stabilize live api memory and reduce internal cache churn","description":"The native VPS deployment is repeatedly OOM-killing islandflow-api.service during live operation. The API live cache is retaining oversized channel histories and rewriting large Redis lists on every flush, which drives multi-GB Bun RSS and heavy loopback traffic between the API, Redis, NATS, and ClickHouse. Implement an emergency VPS mitigation plus repo hardening so unsafe env values, reconnect snapshots, and Redis persistence patterns cannot push the live API back into OOM.","acceptance_criteria":"1. VPS live cache env values are reduced to safe defaults and live redis state is cleared before restart. 2. services/api/src/live.ts enforces server-side live cache caps and clamps snapshot_limit accordingly. 3. Hot generic feed Redis persistence no longer rewrites entire lists on every flush. 4. Metrics/logging expose subscription counts, snapshot sizes, redis flush volume, and API memory trend. 5. Relevant tests pass and the deployment is restarted successfully.","notes":"Implemented local hardening for API live-state limits, incremental generic Redis persistence, live subscription/memory metrics, and safer client/env defaults. Targeted API live tests and the web production build both passed.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T01:30:43Z","created_by":"dirtydishes","updated_at":"2026-05-23T01:39:57Z","started_at":"2026-05-23T01:30:52Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-sc6","title":"fix electron codex bridge preload loading","description":"Electron settings showed the browser-only Desktop Required fallback because the renderer did not see the native islandflowDesktop preload bridge or an Electron user-agent marker. Fix the desktop launch path so ChatGPT/Codex subscription controls are available inside Islandflow Desktop again.","notes":"Reopened after live Electron still showed the browser-only fallback. Follow-up fix adds an explicit preload runtime marker and web runtime detection for that marker so Electron is recognized even when the bridge is not ready and the user agent lacks an Electron token.","status":"closed","priority":1,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-05-20T23:42:58Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:51:43Z","closed_at":"2026-05-20T23:51:43Z","close_reason":"Follow-up fix added an explicit islandflowDesktopRuntime preload marker and taught the web runtime to recognize that marker plus IslandflowDesktop user-agent tokens, so Electron no longer falls into the browser-only fallback when the AI bridge is delayed or unavailable. Desktop build and focused desktop/web tests pass; full web build still blocked by islandflow-c8f.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-hj3","title":"Fix Electron preload for desktop AI bridge","description":"## Why\\nThe desktop settings page reports the native AI bridge as unavailable because Electron fails to load the preload script in local dev.\\n\\n## What\\nUpdate the desktop preload implementation/build so Electron can execute it, restore window.islandflowDesktop, and verify the Copilot settings panel detects the bridge again.\\n\\n## Acceptance Criteria\\n- Electron no longer logs a preload syntax error\\n- window.islandflowDesktop is available in the desktop renderer\\n- The settings page no longer shows bridge unavailable solely because preload failed\\n- Relevant desktop/web tests pass","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T23:16:39Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:20:20Z","started_at":"2026-05-20T23:16:48Z","closed_at":"2026-05-20T23:20:20Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-199","title":"fix desktop copilot fallback inside electron","description":"## Why\\nThe settings page can render the browser-only fallback even when Islandflow is running inside the Electron desktop shell.\\n\\n## What\\nSeparate desktop-shell detection from desktop AI transport state, make the provider recover if the bridge appears late or initial state loading fails, and cover the regression with tests.\\n\\n## Acceptance Criteria\\n- The desktop shell no longer shows the browser-only fallback solely because initial bridge state failed or arrived late\\n- Desktop-only actions can distinguish between missing Electron bridge and transport/auth problems\\n- Automated tests cover the recovery behavior","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T22:30:16Z","created_by":"dirtydishes","updated_at":"2026-05-20T22:37:21Z","started_at":"2026-05-20T22:30:23Z","closed_at":"2026-05-20T22:37:21Z","close_reason":"Fixed desktop-shell Copilot fallback handling, added bridge recovery logic, updated desktop-vs-bridge UI messaging, and added regression tests. Follow-up tracked in islandflow-c8f for unrelated web build blocker.","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/.env.example b/.env.example
index be20b62..2d36859 100644
--- a/.env.example
+++ b/.env.example
@@ -106,7 +106,7 @@ REPLAY_LOG_EVERY=1000
# API live retention (generic channels)
LIVE_LIMIT_DEFAULT=1000
-LIVE_LIMIT_OPTIONS=1000
+LIVE_LIMIT_OPTIONS=100
LIVE_LIMIT_NBBO=1000
LIVE_LIMIT_EQUITIES=1000
LIVE_LIMIT_EQUITY_QUOTES=500
@@ -116,6 +116,7 @@ LIVE_LIMIT_SMART_MONEY=300
LIVE_LIMIT_CLASSIFIER_HITS=300
LIVE_LIMIT_ALERTS=300
LIVE_LIMIT_INFERRED_DARK=300
+LIVE_LIMIT_NEWS=100
LIVE_SCOPED_CACHE_MAX_KEYS=32
LIVE_REDIS_FLUSH_INTERVAL_MS=250
LIVE_REDIS_FLUSH_MAX_ITEMS=100
diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx
index 3057f58..5bf1641 100644
--- a/apps/web/app/terminal.tsx
+++ b/apps/web/app/terminal.tsx
@@ -72,12 +72,12 @@ const parseBoundedInt = (
return Math.max(min, Math.min(max, Math.floor(parsed)));
};
-const LIVE_HOT_WINDOW = parseBoundedInt(process.env.NEXT_PUBLIC_LIVE_HOT_WINDOW, 600, 1, 100000);
+const LIVE_HOT_WINDOW = parseBoundedInt(process.env.NEXT_PUBLIC_LIVE_HOT_WINDOW, 600, 1, 2000);
const LIVE_HOT_WINDOW_OPTIONS = parseBoundedInt(
process.env.NEXT_PUBLIC_LIVE_HOT_WINDOW_OPTIONS,
1200,
1,
- 100000
+ 2000
);
const LIVE_OPTIONS_HEAD_LIMIT = 100;
const LIVE_HISTORY_SOFT_CAP = parseBoundedInt(
diff --git a/deployment/docker/.env.example b/deployment/docker/.env.example
index 4972ada..ff4d7f3 100644
--- a/deployment/docker/.env.example
+++ b/deployment/docker/.env.example
@@ -132,7 +132,7 @@ REPLAY_LOG_EVERY=1000
# API live retention
LIVE_LIMIT_DEFAULT=1000
-LIVE_LIMIT_OPTIONS=1000
+LIVE_LIMIT_OPTIONS=100
LIVE_LIMIT_NBBO=1000
LIVE_LIMIT_EQUITIES=1000
LIVE_LIMIT_EQUITY_QUOTES=500
@@ -142,6 +142,7 @@ LIVE_LIMIT_SMART_MONEY=300
LIVE_LIMIT_CLASSIFIER_HITS=300
LIVE_LIMIT_ALERTS=300
LIVE_LIMIT_INFERRED_DARK=300
+LIVE_LIMIT_NEWS=100
LIVE_SCOPED_CACHE_MAX_KEYS=32
LIVE_REDIS_FLUSH_INTERVAL_MS=250
LIVE_REDIS_FLUSH_MAX_ITEMS=100
diff --git a/services/api/src/index.ts b/services/api/src/index.ts
index 562fb6b..daa013c 100644
--- a/services/api/src/index.ts
+++ b/services/api/src/index.ts
@@ -307,6 +307,35 @@ const subscriptionSockets = new Map>();
const subscriptionDefinitions = new Map();
const liveHeartbeats = new Map>();
+const buildLiveSubscriptionMetrics = (): {
+ liveSocketCount: number;
+ uniqueSubscriptionsByChannel: Partial>;
+ socketFanoutByChannel: Partial>;
+} => {
+ const uniqueSubscriptionsByChannel: Partial> = {};
+ const socketFanoutByChannel: Partial> = {};
+
+ for (const subscription of subscriptionDefinitions.values()) {
+ uniqueSubscriptionsByChannel[subscription.channel] =
+ (uniqueSubscriptionsByChannel[subscription.channel] ?? 0) + 1;
+ }
+
+ for (const [key, sockets] of subscriptionSockets.entries()) {
+ const subscription = subscriptionDefinitions.get(key);
+ if (!subscription || sockets.size === 0) {
+ continue;
+ }
+ socketFanoutByChannel[subscription.channel] =
+ (socketFanoutByChannel[subscription.channel] ?? 0) + sockets.size;
+ }
+
+ return {
+ liveSocketCount: liveSocketSubscriptions.size,
+ uniqueSubscriptionsByChannel,
+ socketFanoutByChannel
+ };
+};
+
const jsonResponse = (body: unknown, status = 200): Response => {
return new Response(JSON.stringify(body), {
status,
@@ -759,6 +788,8 @@ const run = async () => {
const liveState = new LiveStateManager(clickhouse, redis, resolveLiveStateConfig());
await liveState.hydrate();
+ let previousLiveStats = liveState.getStatsSnapshot();
+ let previousMemoryUsage = process.memoryUsage();
const warnLiveLag = (
channel: keyof typeof HOT_LIVE_REDIS_KEYS,
ageMs: number | null | undefined
@@ -778,25 +809,52 @@ const run = async () => {
const liveStateMetricsTimer = setInterval(() => {
const snapshot = liveState.getStatsSnapshot();
const hotFeedHealth = liveState.getHotChannelHealth();
+ const subscriptionMetrics = buildLiveSubscriptionMetrics();
+ const memoryUsage = process.memoryUsage();
const hotFeedLagMs = {
options: snapshot.freshnessAgeMsByKey[HOT_LIVE_REDIS_KEYS.options] ?? null,
equities: snapshot.freshnessAgeMsByKey[HOT_LIVE_REDIS_KEYS.equities] ?? null,
flow: snapshot.freshnessAgeMsByKey[HOT_LIVE_REDIS_KEYS.flow] ?? null,
nbbo: snapshot.freshnessAgeMsByKey[HOT_LIVE_REDIS_KEYS.nbbo] ?? null
};
+ const flushDelta = {
+ redisFlushCount: snapshot.redisFlushCount - previousLiveStats.redisFlushCount,
+ redisFlushItems: snapshot.redisFlushItems - previousLiveStats.redisFlushItems,
+ redisFlushPayloadBytes: snapshot.redisFlushPayloadBytes - previousLiveStats.redisFlushPayloadBytes
+ };
+ const memorySnapshot = {
+ rss_bytes: memoryUsage.rss,
+ heap_used_bytes: memoryUsage.heapUsed,
+ heap_total_bytes: memoryUsage.heapTotal,
+ external_bytes: memoryUsage.external,
+ array_buffers_bytes: memoryUsage.arrayBuffers,
+ rss_delta_bytes: memoryUsage.rss - previousMemoryUsage.rss,
+ heap_used_delta_bytes: memoryUsage.heapUsed - previousMemoryUsage.heapUsed
+ };
logger.info("live cache metrics", {
...snapshot,
hotFeedLagMs,
hotFeedHealth,
+ flushDelta,
+ memorySnapshot,
+ liveSubscriptions: subscriptionMetrics,
snapshotSourceCounts: {
generic_cache_snapshot: snapshot.genericCacheSnapshots,
scoped_clickhouse_snapshot: snapshot.scopedClickHouseSnapshots
}
});
+ metrics.gauge("api.memory.rss_bytes", memoryUsage.rss);
+ metrics.gauge("api.memory.heap_used_bytes", memoryUsage.heapUsed);
+ metrics.gauge("api.live.active_sockets", subscriptionMetrics.liveSocketCount);
+ for (const [channel, count] of Object.entries(subscriptionMetrics.uniqueSubscriptionsByChannel)) {
+ metrics.gauge("api.live.subscription_count", count, { channel });
+ }
warnLiveLag("options", hotFeedLagMs.options);
warnLiveLag("equities", hotFeedLagMs.equities);
warnLiveLag("flow", hotFeedLagMs.flow);
warnLiveLag("nbbo", hotFeedLagMs.nbbo);
+ previousLiveStats = snapshot;
+ previousMemoryUsage = memoryUsage;
}, 60000);
const consumerBindings = [
diff --git a/services/api/src/live.ts b/services/api/src/live.ts
index c8d2886..9687eec 100644
--- a/services/api/src/live.ts
+++ b/services/api/src/live.ts
@@ -89,6 +89,20 @@ const DEFAULT_LIVE_LIMITS: GenericLiveLimits = {
news: 100
};
+export const LIVE_GENERIC_LIMIT_CAPS: GenericLiveLimits = {
+ options: 100,
+ nbbo: 1000,
+ equities: 1000,
+ "equity-quotes": 500,
+ "equity-joins": 500,
+ flow: 500,
+ "smart-money": 300,
+ "classifier-hits": 300,
+ alerts: 300,
+ "inferred-dark": 300,
+ news: 100
+};
+
const DEFAULT_SCOPED_CACHE_MAX_KEYS = 32;
const DEFAULT_REDIS_FLUSH_INTERVAL_MS = 250;
const DEFAULT_REDIS_FLUSH_MAX_ITEMS = 100;
@@ -134,7 +148,7 @@ const parseGenericLimit = (
const key = GENERIC_LIMIT_ENV_KEYS[channel];
const raw = env[key];
if (!raw || raw.trim().length === 0) {
- return fallback;
+ return clampConfiguredLimit(channel, fallback);
}
const parsed = Number(raw);
@@ -143,7 +157,7 @@ const parseGenericLimit = (
return fallback;
}
- const bounded = Math.max(MIN_GENERIC_LIMIT, Math.min(MAX_GENERIC_LIMIT, Math.floor(parsed)));
+ const bounded = clampConfiguredLimit(channel, Math.min(MAX_GENERIC_LIMIT, parsed));
if (bounded !== parsed) {
console.warn(`Clamped ${key} from ${parsed} to ${bounded}`);
}
@@ -226,7 +240,7 @@ const extractFreshnessTs = (channel: LiveGenericChannel, item: any): number | nu
};
export const resolveLiveStateConfig = (env: NodeJS.ProcessEnv = process.env): LiveStateConfig => ({
- limits: resolveGenericLiveLimits(env),
+ limits: clampGenericLimitMap(resolveGenericLiveLimits(env)),
scopedCacheMaxKeys: parsePositiveInt(env.LIVE_SCOPED_CACHE_MAX_KEYS, DEFAULT_SCOPED_CACHE_MAX_KEYS),
redisFlushIntervalMs: parsePositiveInt(
env.LIVE_REDIS_FLUSH_INTERVAL_MS,
@@ -559,7 +573,8 @@ const insertNewestFirst = (
};
};
-type BufferedRedisWrite = {
+type BufferedRedisRewrite = {
+ mode: "rewrite";
listKey: string;
cursorField: string;
items: unknown[];
@@ -568,9 +583,64 @@ type BufferedRedisWrite = {
updates: number;
};
+type BufferedRedisAppend = {
+ mode: "append";
+ listKey: string;
+ cursorField: string;
+ payloads: string[];
+ limit: number;
+ cursor: Cursor | null;
+ updates: number;
+};
+
+type BufferedRedisWrite = BufferedRedisRewrite | BufferedRedisAppend;
+
+export type LiveStateStatsSnapshot = {
+ genericHydrateFromRedis: number;
+ genericHydrateFromClickHouse: number;
+ genericCacheSnapshots: number;
+ scopedClickHouseSnapshots: number;
+ trimOperations: number;
+ redisFlushCount: number;
+ redisFlushItems: number;
+ redisFlushPayloadBytes: number;
+ cacheEvictions: number;
+ outOfOrderEvents: number;
+ cacheDepthByKey: Record;
+ freshnessAgeMsByKey: Record;
+ snapshotItemsByChannel: Record;
+};
+
const isLiveStateConfig = (value: GenericLiveLimits | LiveStateConfig): value is LiveStateConfig =>
"limits" in value;
+const clampConfiguredLimit = (channel: LiveGenericChannel, value: number): number =>
+ Math.max(MIN_GENERIC_LIMIT, Math.min(LIVE_GENERIC_LIMIT_CAPS[channel], Math.floor(value)));
+
+const clampGenericLimitMap = (limits: GenericLiveLimits): GenericLiveLimits =>
+ Object.fromEntries(
+ (Object.keys(LIVE_GENERIC_LIMIT_CAPS) as LiveGenericChannel[]).map((channel) => [
+ channel,
+ clampConfiguredLimit(channel, limits[channel] ?? DEFAULT_LIVE_LIMITS[channel])
+ ])
+ ) as GenericLiveLimits;
+
+const normalizeLiveStateConfig = (config: GenericLiveLimits | LiveStateConfig): LiveStateConfig => {
+ if (isLiveStateConfig(config)) {
+ return {
+ ...config,
+ limits: clampGenericLimitMap(config.limits)
+ };
+ }
+
+ return {
+ limits: clampGenericLimitMap(config),
+ scopedCacheMaxKeys: DEFAULT_SCOPED_CACHE_MAX_KEYS,
+ redisFlushIntervalMs: DEFAULT_REDIS_FLUSH_INTERVAL_MS,
+ redisFlushMaxItems: DEFAULT_REDIS_FLUSH_MAX_ITEMS
+ };
+};
+
export class LiveStateManager {
private readonly config: LiveStateConfig;
private readonly generic: {
@@ -594,10 +664,12 @@ export class LiveStateManager {
trimOperations: 0,
redisFlushCount: 0,
redisFlushItems: 0,
+ redisFlushPayloadBytes: 0,
cacheEvictions: 0,
outOfOrderEvents: 0,
cacheDepthByKey: new Map(),
- freshnessAgeMsByKey: new Map()
+ freshnessAgeMsByKey: new Map(),
+ snapshotItemsByChannel: new Map()
};
constructor(
@@ -605,14 +677,7 @@ export class LiveStateManager {
private readonly redis: RedisLike | null,
config: GenericLiveLimits | LiveStateConfig = resolveLiveStateConfig()
) {
- this.config = isLiveStateConfig(config)
- ? config
- : {
- limits: config,
- scopedCacheMaxKeys: DEFAULT_SCOPED_CACHE_MAX_KEYS,
- redisFlushIntervalMs: DEFAULT_REDIS_FLUSH_INTERVAL_MS,
- redisFlushMaxItems: DEFAULT_REDIS_FLUSH_MAX_ITEMS
- };
+ this.config = normalizeLiveStateConfig(config);
this.generic = getGenericConfig(this.config.limits);
this.redisFlushTimer =
this.redis && this.redis.isOpen
@@ -630,19 +695,7 @@ export class LiveStateManager {
await this.flushRedisWrites();
}
- getStatsSnapshot(): {
- genericHydrateFromRedis: number;
- genericHydrateFromClickHouse: number;
- genericCacheSnapshots: number;
- scopedClickHouseSnapshots: number;
- trimOperations: number;
- redisFlushCount: number;
- redisFlushItems: number;
- cacheEvictions: number;
- outOfOrderEvents: number;
- cacheDepthByKey: Record;
- freshnessAgeMsByKey: Record;
- } {
+ getStatsSnapshot(): LiveStateStatsSnapshot {
return {
genericHydrateFromRedis: this.stats.genericHydrateFromRedis,
genericHydrateFromClickHouse: this.stats.genericHydrateFromClickHouse,
@@ -651,10 +704,12 @@ export class LiveStateManager {
trimOperations: this.stats.trimOperations,
redisFlushCount: this.stats.redisFlushCount,
redisFlushItems: this.stats.redisFlushItems,
+ redisFlushPayloadBytes: this.stats.redisFlushPayloadBytes,
cacheEvictions: this.stats.cacheEvictions,
outOfOrderEvents: this.stats.outOfOrderEvents,
cacheDepthByKey: Object.fromEntries(this.stats.cacheDepthByKey),
- freshnessAgeMsByKey: Object.fromEntries(this.stats.freshnessAgeMsByKey)
+ freshnessAgeMsByKey: Object.fromEntries(this.stats.freshnessAgeMsByKey),
+ snapshotItemsByChannel: Object.fromEntries(this.stats.snapshotItemsByChannel)
};
}
@@ -676,11 +731,36 @@ export class LiveStateManager {
this.pendingRedisWrites.clear();
for (const write of writes) {
- await this.persistList(write.listKey, write.cursorField, write.items, write.limit, write.cursor);
+ if (write.mode === "rewrite") {
+ await this.persistList(write.listKey, write.cursorField, write.items, write.limit, write.cursor);
+ this.stats.redisFlushItems += write.items.length;
+ this.stats.redisFlushPayloadBytes += write.items.reduce(
+ (total, item) => total + JSON.stringify(item).length,
+ 0
+ );
+ } else {
+ await this.persistListAppend(
+ write.listKey,
+ write.cursorField,
+ write.payloads,
+ write.limit,
+ write.cursor
+ );
+ this.stats.redisFlushItems += write.payloads.length;
+ this.stats.redisFlushPayloadBytes += write.payloads.reduce((total, payload) => total + payload.length, 0);
+ }
this.stats.redisFlushCount += 1;
- this.stats.redisFlushItems += write.items.length;
metrics.count("api.live.redis_flush_count", 1);
- metrics.count("api.live.redis_flush_items", write.items.length);
+ metrics.count(
+ "api.live.redis_flush_items",
+ write.mode === "rewrite" ? write.items.length : write.payloads.length
+ );
+ metrics.count(
+ "api.live.redis_flush_payload_bytes",
+ write.mode === "rewrite"
+ ? write.items.reduce((total, item) => total + JSON.stringify(item).length, 0)
+ : write.payloads.reduce((total, payload) => total + payload.length, 0)
+ );
}
}
@@ -739,7 +819,12 @@ export class LiveStateManager {
}
}
- private queueRedisWrite(
+ private recordSnapshotItems(channel: LiveSubscription["channel"], count: number): void {
+ this.stats.snapshotItemsByChannel.set(channel, count);
+ metrics.gauge("api.live.snapshot_items", count, { channel });
+ }
+
+ private queueRedisRewrite(
listKey: string,
cursorField: string,
items: unknown[],
@@ -751,7 +836,8 @@ export class LiveStateManager {
}
const existing = this.pendingRedisWrites.get(listKey);
- const write: BufferedRedisWrite = {
+ const write: BufferedRedisRewrite = {
+ mode: "rewrite",
listKey,
cursorField,
items: [...items],
@@ -765,6 +851,51 @@ export class LiveStateManager {
}
}
+ private queueGenericRedisWrite(
+ listKey: string,
+ cursorField: string,
+ item: unknown,
+ items: unknown[],
+ limit: number,
+ cursor: Cursor | null,
+ forceRewrite = false
+ ): void {
+ if (!this.redis?.isOpen) {
+ return;
+ }
+
+ const existing = this.pendingRedisWrites.get(listKey);
+ const nextUpdateCount = (existing?.updates ?? 0) + 1;
+ if (forceRewrite || existing?.mode === "rewrite") {
+ const write: BufferedRedisRewrite = {
+ mode: "rewrite",
+ listKey,
+ cursorField,
+ items: [...items],
+ limit,
+ cursor,
+ updates: nextUpdateCount
+ };
+ this.pendingRedisWrites.set(listKey, write);
+ } else {
+ const payload = JSON.stringify(item);
+ const write: BufferedRedisAppend = {
+ mode: "append",
+ listKey,
+ cursorField,
+ payloads: [...(existing?.mode === "append" ? existing.payloads : []), payload],
+ limit,
+ cursor,
+ updates: nextUpdateCount
+ };
+ this.pendingRedisWrites.set(listKey, write);
+ }
+
+ if (nextUpdateCount >= this.config.redisFlushMaxItems) {
+ void this.flushRedisWrites();
+ }
+ }
+
async hydrate(): Promise {
const channels = Object.keys(this.generic) as LiveGenericChannel[];
await Promise.all(channels.map((channel) => this.hydrateGeneric(channel)));
@@ -818,6 +949,7 @@ export class LiveStateManager {
const backfill = await fetchRecentOptionPrints(this.clickhouse, limit, undefined, storageFilters);
items = mergeSnapshotBackfill(cached, backfill, limit, (entry) => ({ ts: entry.ts, seq: entry.seq }));
}
+ this.recordSnapshotItems(subscription.channel, items.length);
return {
subscription,
items,
@@ -830,6 +962,7 @@ export class LiveStateManager {
const items = (this.genericItems.get("options") ?? [])
.filter((entry) => matchesOptionPrintFilters(entry, subscription.filters))
.slice(0, limit);
+ this.recordSnapshotItems(subscription.channel, items.length);
return {
subscription,
items,
@@ -844,6 +977,7 @@ export class LiveStateManager {
const items = (this.genericItems.get("flow") ?? [])
.filter((entry) => matchesFlowPacketFilters(entry, subscription.filters))
.slice(0, limit);
+ this.recordSnapshotItems(subscription.channel, items.length);
return {
subscription,
items,
@@ -865,6 +999,7 @@ export class LiveStateManager {
const backfill = await fetchRecentEquityPrints(this.clickhouse, limit, filters);
items = mergeSnapshotBackfill(cached, backfill, limit, config.cursor);
}
+ this.recordSnapshotItems(subscription.channel, items.length);
return {
subscription,
items,
@@ -874,6 +1009,7 @@ export class LiveStateManager {
}
this.stats.genericCacheSnapshots += 1;
const items = (this.genericItems.get("equities") ?? []).slice(0, limit);
+ this.recordSnapshotItems(subscription.channel, items.length);
return {
subscription,
items,
@@ -889,6 +1025,7 @@ export class LiveStateManager {
}
this.touchAccess(this.candleAccess, key);
const items = this.candleItems.get(key) ?? [];
+ this.recordSnapshotItems(subscription.channel, items.length);
return {
subscription,
items,
@@ -904,6 +1041,7 @@ export class LiveStateManager {
}
this.touchAccess(this.overlayAccess, key);
const items = this.overlayItems.get(key) ?? [];
+ this.recordSnapshotItems(subscription.channel, items.length);
return {
subscription,
items,
@@ -916,6 +1054,7 @@ export class LiveStateManager {
this.stats.genericCacheSnapshots += 1;
const limit = snapshotLimitFor(subscription, config.limit);
const items = (this.genericItems.get(subscription.channel) ?? []).slice(0, limit);
+ this.recordSnapshotItems(subscription.channel, items.length);
return {
subscription,
items,
@@ -951,7 +1090,7 @@ export class LiveStateManager {
if (nextState.items.length > 0) {
this.updateFreshnessMetric(key, "equity-candles", nextState.items[0]);
}
- this.queueRedisWrite(key, cursorField, nextState.items, CHART_LIMITS.candles, cursor);
+ this.queueRedisRewrite(key, cursorField, nextState.items, CHART_LIMITS.candles, cursor);
return cursor;
}
case "equity-overlay": {
@@ -977,7 +1116,7 @@ export class LiveStateManager {
if (nextState.items.length > 0) {
this.updateFreshnessMetric(key, "equity-overlay", nextState.items[0]);
}
- this.queueRedisWrite(key, cursorField, nextState.items, CHART_LIMITS.overlay, cursor);
+ this.queueRedisRewrite(key, cursorField, nextState.items, CHART_LIMITS.overlay, cursor);
return cursor;
}
default: {
@@ -1007,7 +1146,15 @@ export class LiveStateManager {
if (nextState.items.length > 0) {
this.updateFreshnessMetric(config.redisKey, channel, nextState.items[0]);
}
- this.queueRedisWrite(config.redisKey, config.cursorField, nextState.items, config.limit, cursor);
+ this.queueGenericRedisWrite(
+ config.redisKey,
+ config.cursorField,
+ parsed,
+ nextState.items,
+ config.limit,
+ cursor,
+ nextState.outOfOrder
+ );
return cursor;
}
}
@@ -1102,4 +1249,23 @@ export class LiveStateManager {
this.stats.cacheDepthByKey.set(listKey, Math.min(items.length, limit));
await this.redis.hSet(CURSOR_HASH_KEY, cursorField, JSON.stringify(cursor));
}
+
+ private async persistListAppend(
+ listKey: string,
+ cursorField: string,
+ payloads: string[],
+ limit: number,
+ cursor: Cursor | null
+ ): Promise {
+ if (!this.redis?.isOpen) {
+ return;
+ }
+
+ for (const payload of payloads) {
+ await this.redis.lPush(listKey, payload);
+ }
+ await this.redis.lTrim(listKey, 0, limit - 1);
+ this.stats.trimOperations += 1;
+ await this.redis.hSet(CURSOR_HASH_KEY, cursorField, JSON.stringify(cursor));
+ }
}
diff --git a/services/api/tests/live.test.ts b/services/api/tests/live.test.ts
index 78807ca..a62fe3b 100644
--- a/services/api/tests/live.test.ts
+++ b/services/api/tests/live.test.ts
@@ -27,6 +27,7 @@ const makeClickHouse = (
const makeRedis = () => {
const lists = new Map();
const hashes = new Map>();
+ let clearTrimCount = 0;
return {
isOpen: true,
@@ -41,6 +42,9 @@ const makeRedis = () => {
},
async lTrim(key: string, start: number, stop: number) {
const next = lists.get(key) ?? [];
+ if (start > stop) {
+ clearTrimCount += 1;
+ }
lists.set(key, start > stop ? [] : next.slice(start, stop + 1));
return "OK";
},
@@ -52,6 +56,9 @@ const makeRedis = () => {
hash.set(field, value);
hashes.set(key, hash);
return 1;
+ },
+ getClearTrimCount() {
+ return clearTrimCount;
}
};
};
@@ -64,8 +71,8 @@ describe("LiveStateManager", () => {
LIVE_LIMIT_FLOW: "bad"
} as NodeJS.ProcessEnv);
- expect(limits.options).toBe(777);
- expect(limits.nbbo).toBe(100000);
+ expect(limits.options).toBe(100);
+ expect(limits.nbbo).toBe(1000);
expect(limits.flow).toBe(500);
expect(limits["equity-quotes"]).toBe(500);
expect(limits.alerts).toBe(300);
@@ -209,11 +216,13 @@ describe("LiveStateManager", () => {
const flushed = await redis.lRange("live:flow", 0, 99);
expect(persisted).toHaveLength(0);
expect(flushed).toHaveLength(2);
+ expect(redis.getClearTrimCount()).toBe(0);
const stats = manager.getStatsSnapshot();
expect(stats.trimOperations).toBeGreaterThan(0);
expect(stats.redisFlushCount).toBeGreaterThan(0);
expect(stats.cacheDepthByKey["live:flow"]).toBe(2);
+ expect(stats.redisFlushPayloadBytes).toBeGreaterThan(0);
});
it("reorders out-of-order live events without dropping newest-first semantics", async () => {
@@ -1074,6 +1083,33 @@ describe("LiveStateManager", () => {
expect(stats.scopedClickHouseSnapshots).toBe(1);
});
+ it("clamps oversized snapshot requests to the server-side channel cap", async () => {
+ const manager = new LiveStateManager(makeClickHouse(), null);
+ const now = Date.now();
+
+ for (let idx = 0; idx < 120; idx += 1) {
+ await manager.ingest("options", {
+ source_ts: now + idx,
+ ingest_ts: now + idx + 1,
+ seq: idx + 1,
+ trace_id: `opt-${idx + 1}`,
+ ts: now + idx,
+ option_contract_id: `SPY-2025-01-17-${500 + idx}-C`,
+ price: 1,
+ size: 10,
+ exchange: "X"
+ });
+ }
+
+ const snapshot = await manager.getSnapshot({
+ channel: "options",
+ snapshot_limit: 10_000
+ });
+
+ expect(snapshot.items).toHaveLength(100);
+ expect(manager.getStatsSnapshot().snapshotItemsByChannel.options).toBe(100);
+ });
+
it("keeps backend channel health healthy when a scoped query is quiet", async () => {
const manager = new LiveStateManager(makeClickHouse(() => []), null);
const now = Date.now();
From 20397fdef37e03fb170aa48373b9df8f29897536 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 22 May 2026 21:42:55 -0400
Subject: [PATCH 02/45] serialize redis flushes during api shutdown
---
services/api/src/live.ts | 50 +++++++++++++++++++++++++++++++++-------
1 file changed, 42 insertions(+), 8 deletions(-)
diff --git a/services/api/src/live.ts b/services/api/src/live.ts
index 9687eec..2df3969 100644
--- a/services/api/src/live.ts
+++ b/services/api/src/live.ts
@@ -614,6 +614,9 @@ export type LiveStateStatsSnapshot = {
const isLiveStateConfig = (value: GenericLiveLimits | LiveStateConfig): value is LiveStateConfig =>
"limits" in value;
+const isRedisClientClosedError = (error: unknown): boolean =>
+ error instanceof Error && error.message.toLowerCase().includes("client is closed");
+
const clampConfiguredLimit = (channel: LiveGenericChannel, value: number): number =>
Math.max(MIN_GENERIC_LIMIT, Math.min(LIVE_GENERIC_LIMIT_CAPS[channel], Math.floor(value)));
@@ -656,6 +659,7 @@ export class LiveStateManager {
private readonly overlayAccess = new Map();
private readonly pendingRedisWrites = new Map();
private readonly redisFlushTimer: ReturnType | null;
+ private redisFlushInFlight: Promise | null = null;
private readonly stats = {
genericHydrateFromRedis: 0,
genericHydrateFromClickHouse: 0,
@@ -723,6 +727,22 @@ export class LiveStateManager {
}
async flushRedisWrites(): Promise {
+ if (this.redisFlushInFlight) {
+ return this.redisFlushInFlight;
+ }
+
+ this.redisFlushInFlight = this.flushRedisWritesInternal();
+ try {
+ await this.redisFlushInFlight;
+ } finally {
+ this.redisFlushInFlight = null;
+ if (this.pendingRedisWrites.size > 0 && this.redis?.isOpen) {
+ void this.flushRedisWrites();
+ }
+ }
+ }
+
+ private async flushRedisWritesInternal(): Promise {
if (!this.redis?.isOpen) {
return;
}
@@ -732,20 +752,34 @@ export class LiveStateManager {
for (const write of writes) {
if (write.mode === "rewrite") {
- await this.persistList(write.listKey, write.cursorField, write.items, write.limit, write.cursor);
+ try {
+ await this.persistList(write.listKey, write.cursorField, write.items, write.limit, write.cursor);
+ } catch (error) {
+ if (isRedisClientClosedError(error)) {
+ return;
+ }
+ throw error;
+ }
this.stats.redisFlushItems += write.items.length;
this.stats.redisFlushPayloadBytes += write.items.reduce(
(total, item) => total + JSON.stringify(item).length,
0
);
} else {
- await this.persistListAppend(
- write.listKey,
- write.cursorField,
- write.payloads,
- write.limit,
- write.cursor
- );
+ try {
+ await this.persistListAppend(
+ write.listKey,
+ write.cursorField,
+ write.payloads,
+ write.limit,
+ write.cursor
+ );
+ } catch (error) {
+ if (isRedisClientClosedError(error)) {
+ return;
+ }
+ throw error;
+ }
this.stats.redisFlushItems += write.payloads.length;
this.stats.redisFlushPayloadBytes += write.payloads.reduce((total, payload) => total + payload.length, 0);
}
From 5a68a3e38e590fe9fca59783e1506652c79a0d44 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 22 May 2026 21:50:35 -0400
Subject: [PATCH 03/45] document live api stabilization rollout
---
.beads/issues.jsonl | 2 +-
.../2026-05-22-stabilize-live-api-memory.html | 810 ++++++++++++++++++
2 files changed, 811 insertions(+), 1 deletion(-)
create mode 100644 docs/turns/2026-05-22-stabilize-live-api-memory.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index c6b5525..2b12057 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,4 +1,4 @@
-{"_type":"issue","id":"islandflow-thp","title":"stabilize live api memory and reduce internal cache churn","description":"The native VPS deployment is repeatedly OOM-killing islandflow-api.service during live operation. The API live cache is retaining oversized channel histories and rewriting large Redis lists on every flush, which drives multi-GB Bun RSS and heavy loopback traffic between the API, Redis, NATS, and ClickHouse. Implement an emergency VPS mitigation plus repo hardening so unsafe env values, reconnect snapshots, and Redis persistence patterns cannot push the live API back into OOM.","acceptance_criteria":"1. VPS live cache env values are reduced to safe defaults and live redis state is cleared before restart. 2. services/api/src/live.ts enforces server-side live cache caps and clamps snapshot_limit accordingly. 3. Hot generic feed Redis persistence no longer rewrites entire lists on every flush. 4. Metrics/logging expose subscription counts, snapshot sizes, redis flush volume, and API memory trend. 5. Relevant tests pass and the deployment is restarted successfully.","notes":"Implemented local hardening for API live-state limits, incremental generic Redis persistence, live subscription/memory metrics, and safer client/env defaults. Targeted API live tests and the web production build both passed.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T01:30:43Z","created_by":"dirtydishes","updated_at":"2026-05-23T01:39:57Z","started_at":"2026-05-23T01:30:52Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-thp","title":"stabilize live api memory and reduce internal cache churn","description":"The native VPS deployment is repeatedly OOM-killing islandflow-api.service during live operation. The API live cache is retaining oversized channel histories and rewriting large Redis lists on every flush, which drives multi-GB Bun RSS and heavy loopback traffic between the API, Redis, NATS, and ClickHouse. Implement an emergency VPS mitigation plus repo hardening so unsafe env values, reconnect snapshots, and Redis persistence patterns cannot push the live API back into OOM.","acceptance_criteria":"1. VPS live cache env values are reduced to safe defaults and live redis state is cleared before restart. 2. services/api/src/live.ts enforces server-side live cache caps and clamps snapshot_limit accordingly. 3. Hot generic feed Redis persistence no longer rewrites entire lists on every flush. 4. Metrics/logging expose subscription counts, snapshot sizes, redis flush volume, and API memory trend. 5. Relevant tests pass and the deployment is restarted successfully.","notes":"Implemented and deployed the live-state hardening to the VPS. Final validation after restart showed the API around 120 MB RSS with capped live cache depths and clean systemd restarts.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T01:30:43Z","created_by":"dirtydishes","updated_at":"2026-05-23T01:50:29Z","started_at":"2026-05-23T01:30:52Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-sc6","title":"fix electron codex bridge preload loading","description":"Electron settings showed the browser-only Desktop Required fallback because the renderer did not see the native islandflowDesktop preload bridge or an Electron user-agent marker. Fix the desktop launch path so ChatGPT/Codex subscription controls are available inside Islandflow Desktop again.","notes":"Reopened after live Electron still showed the browser-only fallback. Follow-up fix adds an explicit preload runtime marker and web runtime detection for that marker so Electron is recognized even when the bridge is not ready and the user agent lacks an Electron token.","status":"closed","priority":1,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-05-20T23:42:58Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:51:43Z","closed_at":"2026-05-20T23:51:43Z","close_reason":"Follow-up fix added an explicit islandflowDesktopRuntime preload marker and taught the web runtime to recognize that marker plus IslandflowDesktop user-agent tokens, so Electron no longer falls into the browser-only fallback when the AI bridge is delayed or unavailable. Desktop build and focused desktop/web tests pass; full web build still blocked by islandflow-c8f.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-hj3","title":"Fix Electron preload for desktop AI bridge","description":"## Why\\nThe desktop settings page reports the native AI bridge as unavailable because Electron fails to load the preload script in local dev.\\n\\n## What\\nUpdate the desktop preload implementation/build so Electron can execute it, restore window.islandflowDesktop, and verify the Copilot settings panel detects the bridge again.\\n\\n## Acceptance Criteria\\n- Electron no longer logs a preload syntax error\\n- window.islandflowDesktop is available in the desktop renderer\\n- The settings page no longer shows bridge unavailable solely because preload failed\\n- Relevant desktop/web tests pass","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T23:16:39Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:20:20Z","started_at":"2026-05-20T23:16:48Z","closed_at":"2026-05-20T23:20:20Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-199","title":"fix desktop copilot fallback inside electron","description":"## Why\\nThe settings page can render the browser-only fallback even when Islandflow is running inside the Electron desktop shell.\\n\\n## What\\nSeparate desktop-shell detection from desktop AI transport state, make the provider recover if the bridge appears late or initial state loading fails, and cover the regression with tests.\\n\\n## Acceptance Criteria\\n- The desktop shell no longer shows the browser-only fallback solely because initial bridge state failed or arrived late\\n- Desktop-only actions can distinguish between missing Electron bridge and transport/auth problems\\n- Automated tests cover the recovery behavior","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T22:30:16Z","created_by":"dirtydishes","updated_at":"2026-05-20T22:37:21Z","started_at":"2026-05-20T22:30:23Z","closed_at":"2026-05-20T22:37:21Z","close_reason":"Fixed desktop-shell Copilot fallback handling, added bridge recovery logic, updated desktop-vs-bridge UI messaging, and added regression tests. Follow-up tracked in islandflow-c8f for unrelated web build blocker.","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/docs/turns/2026-05-22-stabilize-live-api-memory.html b/docs/turns/2026-05-22-stabilize-live-api-memory.html
new file mode 100644
index 0000000..d2b48e2
--- /dev/null
+++ b/docs/turns/2026-05-22-stabilize-live-api-memory.html
@@ -0,0 +1,810 @@
+
+
+
+
+
+ Turn Record: Stabilize Live API Memory
+
+
+
+
+
+ Turn Record · May 22, 2026
+
Stabilize Live API Memory and Internal Traffic
+
+ The Islandflow live API was repeatedly getting OOM-killed on the VPS because the hot live
+ cache could retain oversized channel windows and rewrite whole Redis lists at high
+ frequency. This turn applied an immediate server-side mitigation, hardened the API cache
+ path in code, and rolled the changes onto the native systemd deployment.
+
+
+
+ Branch
+ stabilize-live-api-memory
+
+
+ Beads
+ islandflow-thp
+
+
+ Deployment
+ Native systemd user services on the VPS
+
+
+ Primary Outcome
+ API RSS returned to roughly 115-130 MB after rollout
+
+
+
+
+
+
+
Summary
+
+ The live API is now bounded in three layers instead of trusting environment values and
+ reconnect behavior. First, the VPS .env was reset to safer live-window
+ values and the oversized Redis hot-cache keys were cleared. Second, the API now clamps
+ generic live cache limits per channel in code. Third, generic live feed persistence now
+ appends deltas into Redis instead of cloning and rewriting entire lists on every flush.
+
+
+ Observed on the VPS after rollout:
+ the API stayed healthy through restart, minute metrics showed much smaller cache depths,
+ and the kernel did not log any new Bun OOM kill after the hardened restart.
+
+
+
+
+
Changes Made
+
+
+ Added channel-specific hard caps in
+ services/api/src/live.ts so oversized
+ LIVE_LIMIT_* values are clamped before use.
+
+
+ Changed generic live Redis persistence from full-list rewrite behavior to append-plus-trim,
+ with rewrite fallback only when the in-memory ordering has to be rebuilt.
+
+
+ Serialized Redis flushes during shutdown so service restarts do not race with a closing
+ Redis client.
+
+
+ Added API minute-log visibility for live subscription counts, Redis flush deltas,
+ payload bytes, snapshot sizes, and process memory usage.
+
+
+ Tightened the browser-exposed live window caps in
+ apps/web/app/terminal.tsx and aligned the tracked env examples with the safer
+ production defaults, including LIVE_LIMIT_NEWS.
+
+
+ Applied the emergency mitigation directly on the VPS:
+ updated /home/delta/islandflow/.env, created
+ /home/delta/islandflow/.env.backup-2026-05-22-2131, deleted stale
+ live:* Redis keys, rebuilt the web app, and restarted
+ islandflow-api.service and islandflow-web.service.
+
+
+
+
+
+
Context
+
+ The VPS was killing islandflow-api.service several times on May 22, 2026.
+ Kernel logs showed Bun reaching roughly 8-9 GiB RSS inside the API service cgroup before
+ the OOM killer stepped in. The API minute logs also showed channel depths pinned at
+ 10000 for multiple feeds, plus massive cumulative Redis rewrite churn.
+
+
+ Most of the “huge bandwidth” in btop was local loopback traffic: Bun talking
+ to Redis, NATS, and ClickHouse on 127.0.0.1. That meant the problem was not a
+ public-edge flood, it was the live cache architecture multiplying internal work on the box.
+
+
+
+
+
Important Implementation Details
+
+
+
API hardening
+
+
+ Hard caps now bound generic channel windows even if env values drift upward.
+
+
+ snapshot_limit is still honored, but only up to the lower of the request,
+ the configured limit, and the safe channel cap.
+
+
+ Generic feeds use incremental Redis appends; scoped candle and overlay caches still
+ use full rewrites because they are much smaller and keyed differently.
+
+
+
+
+
Operational changes
+
+
+ The VPS now runs with a much smaller hot live footprint:
+ options 100, flow 500, alerts 300,
+ news 100.
+
+
+ Old Redis hot-cache keys were deleted so the API did not rehydrate oversized lists on boot.
+
+
+ The web app was rebuilt on the VPS checkout after switching that checkout onto
+ stabilize-live-api-memory.
+
+
+
+
+
+
+
+
Relevant Diff Snippets
+
+ These snippets are rendered with the Diffs library from
+ diffs.com, with a plain-text fallback kept inline in the file.
+
+
+
+
services/api/src/live.ts: hard caps and append-based generic Redis flushes
+
+
+ Plain-text fallback
+
Added LIVE_GENERIC_LIMIT_CAPS, clamped env/configured limits, changed generic writes from
+queueRedisWrite(items:[...items]) to queueGenericRedisWrite(item, items, forceRewrite), and split
+Redis persistence into rewrite and append paths with shutdown-safe flush serialization.
+
+
+
+
+
services/api/src/index.ts: minute metrics now include memory and live subscription visibility
+
+
+ Plain-text fallback
+
Added buildLiveSubscriptionMetrics(), previous snapshot tracking, flush delta logging,
+memory snapshots, and gauges for RSS, heap used, active sockets, and per-channel subscriptions.
+
+
+
+
+
.env.example and apps/web/app/terminal.tsx: safer default windows
+
+
+ Plain-text fallback
+
Reduced LIVE_LIMIT_OPTIONS in tracked examples to 100, added LIVE_LIMIT_NEWS=100,
+and lowered the client-exposed maximum live hot windows from 100000 to 2000.
+
+
+
+
+
+
+
Expected Impact for End-Users
+
+
+ The hosted app should stop disappearing behind API restarts caused by the kernel OOM killer.
+
+
+ Live feeds should still feel current, but the server will retain a tighter hot window instead of
+ hoarding oversized in-memory histories.
+
+
+ The operator experience on the VPS should improve because internal loopback churn is materially lower.
+
+
+
+
+
+
Validation
+
+
+ Local API test gate passed:
+ bun test services/api/tests/live.test.ts
+
+
+ Local web production build passed:
+ bun --cwd=apps/web run build
+
+
+ VPS mitigation applied successfully. Redis reported 1524 live keys removed before restart.
+
+
+ After mitigation restart, systemctl --user status islandflow-api.service showed the
+ API at about 84 MB RSS instead of multi-GB startup drift.
+
+
+ After rolling the hardened branch onto the VPS, the API minute log at
+ 2026-05-22 21:44:11 EDT showed:
+
+
+
+
+ 119.6 MB
+ API RSS from the minute memory snapshot
+
+
+ 100
+ live:options depth
+
+
+ 500
+ live:flow, live:alerts, and live:equity-quotes caps held
+
+
+ 34,559
+ Redis flush items in that minute delta
+
+
+ 9.18 MB
+ Redis flush payload bytes in that minute delta
+
+
+ No new OOM
+ Kernel logs after the hardened restart
+
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
+ The new minute metrics are cumulative plus delta-based. They are much more useful than the old
+ absolute counters, but they still reset on process restart.
+
+
+ snapshotItemsByChannel remains empty when no live websocket clients are connected.
+ That is expected because snapshots are only recorded when a snapshot is actually served.
+
+
+ Quiet feeds such as news and inferred-dark can still show very old freshness ages in logs.
+ That reflects inactivity, not a broken hot path.
+
+
+ The append-based Redis path deliberately falls back to a rewrite when out-of-order live events
+ require the in-memory ordering to be rebuilt. That keeps correctness ahead of theoretical write minimization.
+
+
+
+
+
+
Follow-up Work
+
+
+ Add explicit alerting for repeated API RSS growth and for minute-level flush deltas that jump far above the new baseline.
+
+
+ Decide whether quiet-channel freshness logs should suppress extremely stale values for feeds like news to reduce operator noise.
+
+
+ Consider moving the live cache metrics into a dashboard view so operators do not need to parse journal lines manually.
+
+
+
+
+
+
+
+
+
From db7370052fdd1b783585cd21f36e6f16e4ce00ce Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Fri, 22 May 2026 22:25:02 -0400
Subject: [PATCH 04/45] add an anatomy page for options flow
---
.beads/issues.jsonl | 3 +-
docs/anatomy.html | 954 ++++++++++++++++++
docs/index.html | 222 +++-
...6-05-22-add-options-anatomy-explainer.html | 584 +++++++++++
4 files changed, 1711 insertions(+), 52 deletions(-)
create mode 100644 docs/anatomy.html
create mode 100644 docs/turns/2026-05-22-add-options-anatomy-explainer.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 2b12057..fb97a9f 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,4 +1,4 @@
-{"_type":"issue","id":"islandflow-thp","title":"stabilize live api memory and reduce internal cache churn","description":"The native VPS deployment is repeatedly OOM-killing islandflow-api.service during live operation. The API live cache is retaining oversized channel histories and rewriting large Redis lists on every flush, which drives multi-GB Bun RSS and heavy loopback traffic between the API, Redis, NATS, and ClickHouse. Implement an emergency VPS mitigation plus repo hardening so unsafe env values, reconnect snapshots, and Redis persistence patterns cannot push the live API back into OOM.","acceptance_criteria":"1. VPS live cache env values are reduced to safe defaults and live redis state is cleared before restart. 2. services/api/src/live.ts enforces server-side live cache caps and clamps snapshot_limit accordingly. 3. Hot generic feed Redis persistence no longer rewrites entire lists on every flush. 4. Metrics/logging expose subscription counts, snapshot sizes, redis flush volume, and API memory trend. 5. Relevant tests pass and the deployment is restarted successfully.","notes":"Implemented and deployed the live-state hardening to the VPS. Final validation after restart showed the API around 120 MB RSS with capped live cache depths and clean systemd restarts.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T01:30:43Z","created_by":"dirtydishes","updated_at":"2026-05-23T01:50:29Z","started_at":"2026-05-23T01:30:52Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-thp","title":"stabilize live api memory and reduce internal cache churn","description":"The native VPS deployment is repeatedly OOM-killing islandflow-api.service during live operation. The API live cache is retaining oversized channel histories and rewriting large Redis lists on every flush, which drives multi-GB Bun RSS and heavy loopback traffic between the API, Redis, NATS, and ClickHouse. Implement an emergency VPS mitigation plus repo hardening so unsafe env values, reconnect snapshots, and Redis persistence patterns cannot push the live API back into OOM.","acceptance_criteria":"1. VPS live cache env values are reduced to safe defaults and live redis state is cleared before restart. 2. services/api/src/live.ts enforces server-side live cache caps and clamps snapshot_limit accordingly. 3. Hot generic feed Redis persistence no longer rewrites entire lists on every flush. 4. Metrics/logging expose subscription counts, snapshot sizes, redis flush volume, and API memory trend. 5. Relevant tests pass and the deployment is restarted successfully.","notes":"Implemented and deployed the live-state hardening to the VPS. Final validation after restart showed the API around 120 MB RSS with capped live cache depths and clean systemd restarts.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T01:30:43Z","created_by":"dirtydishes","updated_at":"2026-05-23T01:50:41Z","started_at":"2026-05-23T01:30:52Z","closed_at":"2026-05-23T01:50:41Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-sc6","title":"fix electron codex bridge preload loading","description":"Electron settings showed the browser-only Desktop Required fallback because the renderer did not see the native islandflowDesktop preload bridge or an Electron user-agent marker. Fix the desktop launch path so ChatGPT/Codex subscription controls are available inside Islandflow Desktop again.","notes":"Reopened after live Electron still showed the browser-only fallback. Follow-up fix adds an explicit preload runtime marker and web runtime detection for that marker so Electron is recognized even when the bridge is not ready and the user agent lacks an Electron token.","status":"closed","priority":1,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-05-20T23:42:58Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:51:43Z","closed_at":"2026-05-20T23:51:43Z","close_reason":"Follow-up fix added an explicit islandflowDesktopRuntime preload marker and taught the web runtime to recognize that marker plus IslandflowDesktop user-agent tokens, so Electron no longer falls into the browser-only fallback when the AI bridge is delayed or unavailable. Desktop build and focused desktop/web tests pass; full web build still blocked by islandflow-c8f.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-hj3","title":"Fix Electron preload for desktop AI bridge","description":"## Why\\nThe desktop settings page reports the native AI bridge as unavailable because Electron fails to load the preload script in local dev.\\n\\n## What\\nUpdate the desktop preload implementation/build so Electron can execute it, restore window.islandflowDesktop, and verify the Copilot settings panel detects the bridge again.\\n\\n## Acceptance Criteria\\n- Electron no longer logs a preload syntax error\\n- window.islandflowDesktop is available in the desktop renderer\\n- The settings page no longer shows bridge unavailable solely because preload failed\\n- Relevant desktop/web tests pass","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T23:16:39Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:20:20Z","started_at":"2026-05-20T23:16:48Z","closed_at":"2026-05-20T23:20:20Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-199","title":"fix desktop copilot fallback inside electron","description":"## Why\\nThe settings page can render the browser-only fallback even when Islandflow is running inside the Electron desktop shell.\\n\\n## What\\nSeparate desktop-shell detection from desktop AI transport state, make the provider recover if the bridge appears late or initial state loading fails, and cover the regression with tests.\\n\\n## Acceptance Criteria\\n- The desktop shell no longer shows the browser-only fallback solely because initial bridge state failed or arrived late\\n- Desktop-only actions can distinguish between missing Electron bridge and transport/auth problems\\n- Automated tests cover the recovery behavior","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T22:30:16Z","created_by":"dirtydishes","updated_at":"2026-05-20T22:37:21Z","started_at":"2026-05-20T22:30:23Z","closed_at":"2026-05-20T22:37:21Z","close_reason":"Fixed desktop-shell Copilot fallback handling, added bridge recovery logic, updated desktop-vs-bridge UI messaging, and added regression tests. Follow-up tracked in islandflow-c8f for unrelated web build blocker.","dependency_count":0,"dependent_count":0,"comment_count":0}
@@ -72,6 +72,7 @@
{"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-hpf","title":"add anatomy explainer for options print and smart money flow","description":"Create a standalone docs/anatomy.html reference page that explains the end-to-end lifecycle of an options print through enrichment, signal filtering, compute clustering, flow packet creation, smart-money evaluation, classifier hits, alerts, and API/live consumption. The page should be polished, user-readable, and visually strong enough to serve as a reusable reference artifact for both technical and non-technical readers.","notes":"Added docs/anatomy.html as a standalone reference page for the options-print to smart-money pipeline, styled in the repo product register and layered for executive, mixed technical, and operator-level readers. Regenerated docs/index.html so the page is discoverable from the docs surface.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T02:18:48Z","created_by":"dirtydishes","updated_at":"2026-05-23T02:24:58Z","started_at":"2026-05-23T02:18:53Z","closed_at":"2026-05-23T02:24:58Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-4ca","title":"Publish May 21 standup git summary","description":"Create the daily standup-ready git activity summary for 2026-05-21, save the HTML artifact under docs/general, add the required turn document, and push the result so the automation leaves a durable record.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-22T13:03:00Z","created_by":"dirtydishes","updated_at":"2026-05-22T13:05:05Z","started_at":"2026-05-22T13:03:03Z","closed_at":"2026-05-22T13:05:05Z","close_reason":"Created the 2026-05-21 standup summary in docs/general, added the required turn document, and prepared the repo for commit/push.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-hgm","title":"Publish May 20 standup git summary","description":"Create the daily standup-ready git activity summary for 2026-05-20, save the HTML artifact under docs/general, and push the result so the automation leaves a durable record.","status":"closed","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-21T13:02:38Z","created_by":"dirtydishes","updated_at":"2026-05-21T13:05:16Z","closed_at":"2026-05-21T13:05:16Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-4q0","title":"refresh readme app description with current classification approach","description":"Update README intro content to better describe the app's current architecture and include a concise explanation of how Islandflow classifies prints, aligned with smartmoney.md and current services.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-21T01:53:30Z","created_by":"dirtydishes","updated_at":"2026-05-21T01:55:01Z","started_at":"2026-05-21T01:53:33Z","closed_at":"2026-05-21T01:55:01Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/docs/anatomy.html b/docs/anatomy.html
new file mode 100644
index 0000000..bdd3da5
--- /dev/null
+++ b/docs/anatomy.html
@@ -0,0 +1,954 @@
+
+
+
+
+
+ The Anatomy of an Options Print and Smart Money
+
+
+
+
+
+
+ This page explains how a single options print moves through Islandflow under normal market conditions,
+ how the signal gate decides whether compute should care, how a parent flow packet is assembled, and how
+ smart-money, classifier-hit, and alert events emerge from that packet. It is designed as one artifact
+ with three reading depths: executive, mixed technical, and operator-level.
+
+
+
+
+
+
+
+
+
+
Legend
+
Color coding is semantic, not decorative, so you can scan the diagram without relearning the vocabulary.
+
+
+ Raw market or synthetic input
+ Derived compute stage
+ Stored or persisted state
+ API, websocket, or user-facing surface
+
+
+
+
+
+
Main Flow Chart
+
+ The first row shows the common path every print touches. The second row shows the branch between prints
+ that remain tape-only and prints that become packet candidates for smart-money evaluation.
+
+
+
+
+
+
+
Input
+
+ Stage 1
+ Option print candidate arrives
+
+ The source can be a native market adapter or the synthetic adapter. Synthetic mode can also emit a
+ matching NBBO update.
+
+ The service joins recent option NBBO and underlying equity quote context, derives metadata, and
+ computes signal_pass.
+
+
+
+ Stage 3
+ Raw print is written and published
+
+
ClickHouse: option_prints
+
NATS: options.prints
+
+
+
+ Stage 4
+ Signal gate decides if compute should care
+
+ Only signal_pass=true prints are published to options.prints.signal and
+ consumed by compute.
+
+
+
+ Stage 5
+ compute builds or updates a parent cluster
+
+ Nearby signal prints for the same contract are grouped inside the cluster window while NBBO and
+ equity-quote caches supply context.
+
+
+
+ Stage 6
+ API and UI consume the resulting streams
+
+ The API hydrates hot snapshots, history endpoints read ClickHouse, and the terminal surfaces tape,
+ flow, smart-money, classifier, and alert views.
+
+
+
+
+
+
Tape-only branch
+
+
+
+ Branch A
+ Raw print remains visible
+
+ Even if the print does not pass the signal gate, it still exists in ClickHouse and can appear in
+ raw tape or history views.
+
+
+
+ Branch A outcome
+ No compute packet path
+
+ No FlowPacket, no smart-money evaluation, no classifier hits, and no alert emission.
+
+
+
+
+
+
+
+
Smart-money branch
+
+
+
+
+ Branch B
+ Signal print enters compute
+
+ compute subscribes to options.prints.signal, not raw options.prints.
+
+ The packet is scored into a SmartMoneyEvent, which may abstain, produce classifier
+ hits, and finally emit an alert.
+
+
+
+
+
+
+
+
+
+
Executive Read
+
+ The shortest truthful version of the system: not every options print is considered meaningful, and smart
+ money is not detected directly from a single print.
+
+
+
+
+ 1. Tape
+
Every print is stored
+
+ All enriched prints are written to ClickHouse and published to the raw options subject. This preserves
+ evidence even when the print is uninteresting for higher-order inference.
+
+
+
+ 2. Compute
+
Only signal prints reach the parent-event engine
+
+ A print must pass the signal gate before compute clusters it with neighboring prints and builds a
+ packet that represents a possible parent order.
+
+
+
+ 3. Smart money
+
Smart money is a scored interpretation
+
+ The model evaluates the packet using quote quality, aggressor mix, size, structure, DTE, IV, and event
+ context. It can still abstain if the evidence is weak or suppressed.
+
+
+
+
+
+
+
+
Mixed Technical Walkthrough
+
+ This layer is for teammates who know the product and want the exact branching logic without reading
+ through service code first.
+
+
+
+
+
+ Step 1: a candidate print enters ingest-options. In synthetic mode this
+ print was manufactured by the synthetic adapter, which may also emit a synthetic NBBO update for the
+ same contract.
+
+
+ Step 2: the print is enriched with the most recent option NBBO and underlying equity
+ quote at or before the print timestamp. The service derives metadata, execution-side context, and the
+ signal_pass decision.
+
+
+ Step 3: the enriched print is persisted to ClickHouse and published to
+ options.prints. If signal_pass=true, the same print is also published to
+ options.prints.signal.
+
+
+ Step 4: compute subscribes to the signal subject plus NBBO and equity-quote subjects.
+ It does not build packet candidates from every raw print. It only clusters signal prints.
+
+
+ Step 5: compute aggregates nearby signal prints for the same option contract into a
+ cluster, then flushes that cluster into a FlowPacket with features such as total premium,
+ print count, aggressor ratios, NBBO coverage, stale-quote counts, IV context, and structure clues.
+
+
+ Step 6: the packet is transformed into a SmartMoneyEvent. If suppression
+ rules trip or the top profile probability is too weak, the event abstains. Otherwise, it can emit
+ classifier hits and finally an alert with evidence references back to the packet and member prints.
+
+
+
+
+
+
+
+
+
Operator and Code-Level Detail
+
+ This section is for someone tracing the live pipeline, debugging a regression, or trying to understand
+ exactly why a given print surfaced on tape but did or did not become a smart-money event.
+
+
+
+
+ The first fork is the signal gate in ingest-options. The enriched print is always stored and
+ published raw. The only thing signal_pass controls is whether compute receives that print on
+ options.prints.signal.
+
+
+ The compute service maintains separate caches for option NBBO and underlying equity quotes. When signal
+ prints arrive, it flushes aged clusters, extends the active cluster for that contract if the print lands
+ within the configured window, or emits the old cluster and starts a new one.
+
+
+ The cluster becomes a FlowPacket only after compute summarizes parent-level features. That
+ packet then passes through smart-money scoring. The scoring layer derives a profile set such as
+ institutional directional, retail whale, event driven, vol seller, arbitrage, or hedge reactive.
+
+
+ A packet can still fail to produce actionable downstream artifacts. Suppression rules down-rank special
+ print context, stale or missing quote context, and cross-like execution patterns. The top profile must
+ also clear the probability threshold. If it does not, the smart-money event is emitted in abstained form
+ and classifier hits stop there.
+
+
+ If the packet does clear those checks, compute writes and publishes the smart-money event, derives up to
+ a few classifier hits from the top profile set, scores a final alert, and publishes all three derived
+ streams. The API subscribes to those subjects and fans them out into live websocket channels while
+ ClickHouse remains the history source behind /history/*.
+
+
+
+
+
+
+
+
Subject or table
+
Produced by
+
Carries
+
Why it exists
+
+
+
+
+
options.prints
+
ingest-options
+
All enriched option prints
+
Preserves the full tape, even when a print is not interesting enough for compute.
+
+
+
options.prints.signal
+
ingest-options
+
Signal-passing option prints
+
Acts as the compute admission gate so packet building starts from a filtered tape.
+
+
+
flow.packets
+
compute
+
Parent-event candidates
+
Turns several child prints into one summarized event with market-structure features.
+
+
+
flow.smart_money
+
compute
+
Smart-money evaluations
+
Publishes the scored interpretation of a packet, including abstained outcomes.
+
+
+
flow.classifier_hits
+
compute
+
Top classifier consequences
+
Exposes the strongest profile-level labels that downstream UX and alerting can decorate.
+
+
+
flow.alerts
+
compute
+
Alert events with evidence refs
+
Packages the final severity and supporting evidence into a user-facing alert stream.
+
+
+
+
+
+
+
+
+
Normal Path Versus Smart-Money Path
+
+ These two sequences are easy to confuse, especially because both begin with the same enriched tape
+ record.
+
+
+
+
+ Normal market path
+
+ Print arrives, gets enriched, gets stored, appears on the raw tape, and stops there unless it passes
+ the signal gate. This is the dominant path for ordinary or low-signal activity.
+
+
+
+ Smart-money path
+
+ Print arrives, passes the signal gate, joins a cluster, becomes a packet, receives a smart-money score,
+ then may emit classifier hits and an alert if the packet is not suppressed or abstained.
+
+
+
+
+
+
+
+
Annotated Event Sequence
+
+ The example below is the shortest operator-friendly way to think about the branch that leads to a
+ smart-money result.
+
+
+
1. Synthetic or market adapter emits OptionPrint candidate
+2. ingest-options enriches it with latest NBBO and underlying quote context
+3. Enriched print is written to ClickHouse option_prints
+4. Enriched print is published to options.prints
+5. If signal_pass=true, the same print is also published to options.prints.signal
+6. compute consumes options.prints.signal and updates the active contract cluster
+7. Cluster flush builds a FlowPacket with parent-level features
+8. FlowPacket is written to ClickHouse flow_packets and published to flow.packets
+9. compute scores the packet into a SmartMoneyEvent
+10. If suppressed or low-confidence, the SmartMoneyEvent abstains and stops there
+11. Otherwise classifier hits are emitted
+12. Alert scoring emits a final alert with evidence refs to smart-money event, flow packet, and member prints
+13. API subscribes to these streams and exposes them through live websocket channels and ClickHouse-backed history
+
+
+
+
+
What Synthetic Mode Changes
+
+ Synthetic mode can make the upstream generator artificial, but the downstream branch logic stays
+ identical.
+
+
+
+
+ The synthetic adapter constructs an OptionPrint with fields such as
+ execution_iv_source="synthetic_pressure_model", and it may emit a synthetic NBBO for the
+ same contract. From that point forward, the pipeline is the same one used for normal ingest.
+
+
+ That means synthetic smart-money is not a special smart-money subsystem. It is the standard
+ signal-to-packet-to-smart-money pipeline running on synthetic upstream events.
+
+
+
+
+
+
+
Code Anchors
+
+ If you want to confirm this page against the code, these are the most useful entry points.
+
+
+
+
+
services/ingest-options/src/enrichment.ts: enriches the print and decides signal_pass.
+
services/ingest-options/src/index.ts: writes prints and publishes raw versus signal subjects.
+
services/compute/src/index.ts: subscribes to signal prints, maintains clusters, emits packets, smart money, hits, and alerts.
packages/bus/src/subjects.ts: canonical subject names for the pipeline.
+
+
+
+ This document is intended as a living product reference, not a turn artifact. If the packet features,
+ thresholds, or stream names change, update this page alongside the relevant pipeline code.
+
+ Added a standalone docs/anatomy.html reference page that explains the
+ full lifecycle of an options print, from ingest and signal gating through flow packet
+ construction, smart-money scoring, classifier hits, alerts, and API/live consumption.
+ The page is styled to match Islandflow’s product register and layered so exec, mixed
+ technical, and operator-level readers can all use the same artifact.
+
+ The repo now includes a reusable explainer page for one of the most important pieces of
+ Islandflow’s mental model: how a raw or synthetic options print turns into visible tape,
+ a flow packet, and sometimes a smart-money or alert event. Instead of scattering that
+ explanation across chat answers and source code, the new page centralizes the pipeline in
+ a designed HTML document that can be browsed directly under docs/.
+
+
+ Primary outcome: the new page makes the option-print pipeline legible at
+ three reading depths without forcing someone to reconstruct the architecture from service
+ code.
+
+
+
+
+
Changes Made
+
+
+ Added docs/anatomy.html as a standalone explainer page titled
+ The Anatomy of an Options Print and Smart Money.
+
+
+ Built a large flow-chart section that distinguishes the common tape path from the
+ signal-to-packet-to-smart-money branch.
+
+
+ Layered the page into executive, mixed technical, and operator-level explanations so
+ one artifact works for multiple audiences.
+
+
+ Included subject/table mapping, annotated sequence detail, synthetic-mode notes, and
+ code anchors back into the real repo.
+
+
+ Regenerated docs/index.html so the new explainer is discoverable from the
+ existing docs index.
+
+
+
+
+
+
Context
+
+ The user asked for a true flow-chart explanation of what happens when options tape comes
+ in under normal market scenarios and when smart-money behavior is detected, with the
+ important caveat that the current environment is using synthetic prints. The repo already
+ had the implementation details, but not a clear product artifact that unified ingest,
+ compute, storage, bus subjects, and API/live consumption into one readable document.
+
+
+ Because Islandflow’s UI language is already defined as an “evidence console,” the new
+ page needed to feel operational and precise rather than like a generic landing page or a
+ decorative infographic.
+
+
+
+
+
Important Implementation Details
+
+
+
Information architecture
+
+
+ The page starts with a semantic legend and a visual flow board so readers can build
+ the correct mental model before diving into prose.
+
+
+ The explanation then deepens in three layers: executive read, mixed technical
+ walkthrough, and operator/code-level detail.
+
+
+ The normal tape path and the smart-money path are split explicitly so readers do not
+ confuse raw tape visibility with compute-derived inference.
+
+
+
+
+
Design choices
+
+
+ The visual treatment follows the repo’s product register: dark, stable, evidence-first,
+ amber used as a sparse signal, monospace labels for pipeline semantics.
+
+
+ The flow chart is pure HTML and CSS, not a JavaScript diagram dependency, so the
+ page remains portable and straightforward to keep in sync with the repo.
+
+
+ docs/index.html was regenerated with the existing script so the page
+ participates in the current docs navigation surface instead of becoming a hidden one-off.
+
+
+
+
+
+
+
+
Relevant Diff Snippets
+
+ These snippets are rendered with the Diffs library from
+ diffs.com, with a plain-text fallback kept inline.
+
+
+
+
docs/anatomy.html: new explainer page and flow-board structure
+
+
+ Plain-text fallback
+
+ Added docs/anatomy.html
++ Product-register dark evidence-console styling
++ Main flow chart with common path, tape-only branch, and smart-money branch
++ Layered explanation sections for executive, mixed technical, and operator audiences
++ Subject map, annotated sequence, synthetic mode notes, and code anchors
+
+
+
+
+
docs/index.html: regenerated docs surface with new entry count
+
+
+ Plain-text fallback
+
- 35 files shown
++ 47 files shown
+- root/general counts from prior docs set
++ updated counts after regenerating the index, including the new anatomy explainer entry
+
+
+
+
+
+
+
Expected Impact for End-Users
+
+
+ Teammates and operators now have a single place to understand why a print can appear on
+ tape without ever becoming a smart-money event.
+
+
+ The synthetic-print caveat is captured directly in the artifact, which should reduce
+ confusion when debugging or demoing the current environment.
+
+
+ The docs surface becomes more useful as a living product reference, not just a collection
+ of turn records and plans.
+
+
+
+
+
+
Validation
+
+
+ Generated the new page at docs/anatomy.html and verified the title and
+ major sections are present.
+
+
+ Regenerated the docs index with
+ node scripts/generate-docs-index.mjs, which completed successfully and
+ reported 47 entries.
+
+
+ Confirmed the new explainer page is included in the docs surface by regenerating
+ docs/index.html.
+
+
+
+
+ 1
+ new reusable explainer page
+
+
+ 47
+ docs index entries after regeneration
+
+
+ 3
+ reader depth layers on the page
+
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
+ The page is intentionally hand-authored HTML rather than a generated diagram artifact.
+ That keeps it portable, but it also means future pipeline changes should update this page
+ manually.
+
+
+ The docs index regeneration reflects the full current docs/ tree, so the
+ visible counts changed by more than one file compared with the previously committed
+ index.
+
+
+ This validation pass verified structure and generation success, but did not include a
+ browser-rendered visual QA step against multiple viewport sizes.
+
+
+
+
+
+
Follow-up Work
+
+
+ Add reciprocal links from more domain-specific docs such as smartmoney.md
+ back to docs/anatomy.html.
+
+
+ Consider a second reference page focused specifically on one concrete synthetic example,
+ from a burst of prints to the final alert payload.
+
+
+ If the flow-packet feature set evolves, keep the anatomy page in lockstep with those
+ changes so it remains a trustworthy operator reference.
+
+
+
+
+
+
+
+
+
From cbb0891ab76baacf22b2f5ab3ee0b998c9a33e7c Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 23 May 2026 16:25:57 -0400
Subject: [PATCH 05/45] add turn doc for pr 8 reconciliation
---
.beads/issues.jsonl | 2 +-
docs/turns/2026-05-23-reconcile-pr-8.html | 93 +++++++++++++++++++++++
2 files changed, 94 insertions(+), 1 deletion(-)
create mode 100644 docs/turns/2026-05-23-reconcile-pr-8.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index f1c7f22..12572eb 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -21,7 +21,7 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-kgu","title":"Reconcile PR #8 branch with current main","description":"Why this issue exists and what needs to be done: user requested reconciliation for PR #8. Identify the PR #8 branch, merge/rebase with current main, resolve conflicts, validate, and push the updated branch so the PR can merge cleanly.","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T20:14:36Z","created_by":"dirtydishes","updated_at":"2026-05-23T20:14:39Z","started_at":"2026-05-23T20:14:39Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-kgu","title":"Reconcile PR #8 branch with current main","description":"Why this issue exists and what needs to be done: user requested reconciliation for PR #8. Identify the PR #8 branch, merge/rebase with current main, resolve conflicts, validate, and push the updated branch so the PR can merge cleanly.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T20:14:36Z","created_by":"dirtydishes","updated_at":"2026-05-23T20:24:29Z","started_at":"2026-05-23T20:14:39Z","closed_at":"2026-05-23T20:24:29Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-l9h","title":"stop persisting non-signal option prints in clickhouse","description":"Why: non-signal option prints are storage noise and should not be persisted by default.\\n\\nWhat: add OPTIONS_PERSIST_SIGNAL_ONLY env flag (default true), gate option_print inserts in ingest-options, add tests for persistence behavior, update env examples, and document one-off cleanup SQL for existing non-signal rows.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T03:02:32Z","created_by":"dirtydishes","updated_at":"2026-05-23T03:06:34Z","started_at":"2026-05-23T03:02:35Z","closed_at":"2026-05-23T03:06:34Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-2cj","title":"Add Forgejo-first agent workflow guidance to AGENTS.md","description":"Why this issue exists and what needs to be done:\\n- The repository’s canonical home is Forgejo at git.deltaisland.io, but AGENTS.md does not currently direct agents to prefer Forgejo-specific workflows.\\n- Update AGENTS.md so agents treat Forgejo as primary and use the fj CLI for pull request workflows.\\n- Keep existing Beads and completion instructions intact while clarifying remote preference and command usage.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-23T02:51:31Z","created_by":"dirtydishes","updated_at":"2026-05-23T02:55:42Z","closed_at":"2026-05-23T02:55:42Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-xc5","title":"One-time bidirectional git remote backfill between github and forgejo","description":"Perform a one-time sync so github and forgejo contain the same branch/tag refs and historical commits, including pre-transition github history and newer forgejo commits. Document exact commands and validation results.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-21T01:25:05Z","created_by":"dirtydishes","updated_at":"2026-05-21T01:26:19Z","started_at":"2026-05-21T01:25:16Z","closed_at":"2026-05-21T01:26:19Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/docs/turns/2026-05-23-reconcile-pr-8.html b/docs/turns/2026-05-23-reconcile-pr-8.html
new file mode 100644
index 0000000..163b911
--- /dev/null
+++ b/docs/turns/2026-05-23-reconcile-pr-8.html
@@ -0,0 +1,93 @@
+
+
+
+
+
+ Turn Doc - Reconcile PR #8
+
+
+
+
Reconcile PR #8 with main
+
Date: 2026-05-23
+
+
+
Summary
+
Reconciled PR #8 by merging the latest main into stabilize-live-api-memory, resolving the only merge conflict, and pushing the updated branch to Forgejo.
+
+
+
+
Changes Made
+
+
Checked out stabilize-live-api-memory from forgejo/stabilize-live-api-memory.
+
Merged forgejo/main into the PR branch.
+
Resolved merge conflict in .beads/issues.jsonl.
+
Closed Beads issue islandflow-kgu for this reconciliation work.
+
Pushed Beads data and git branch updates to Forgejo.
+
+
+
+
+
Context
+
The user requested reconciliation for PR #8. PR #8 head branch (stabilize-live-api-memory) was behind current main, so the branch needed an integration merge to clear mergeability drift.
+
+
+
+
Important Implementation Details
+
+
The only merge conflict was in Beads tracker data (.beads/issues.jsonl).
+
Conflict resolution preserved both upstream tracker updates and the in-progress reconciliation issue record.
+
No service/application source files required manual code conflict resolution in this reconciliation pass.
+
+
+
+
+
Relevant Diff Snippets
+
Rendered in diffs.com-compatible unified diff style:
+
commit 6584f7d1545019da663ab3ec9719d06e25c5244e
+Merge: db73700 8464287
+Author: dirtydishes
+
++ merge main into stabilize-live-api-memory to reconcile pr 8
+
+MM .beads/issues.jsonl
+
+
+
+
Expected Impact for End-Users
+
End-users should not see functional UI/API changes from this reconciliation itself; the impact is operational: PR #8 can now be merged cleanly against current mainline history.
+
+
+
+
Validation
+
+
Verified branch push to Forgejo succeeded.
+
Verified Beads push (bd dolt push) succeeded.
+
Verified final git state reports branch aligned with forgejo/stabilize-live-api-memory.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
Limitation:fj auth was initially unauthorized in this session.
+
Mitigation: Re-auth was completed by user and confirmed with successful fj pr view 8.
+
Scope note: This turn focused on branch reconciliation, not feature behavior changes.
+
+
+
+
+
Follow-up Work
+
+
Proceed with normal review/merge flow for PR #8 in Forgejo.
+
If additional commits land on main before merge, re-run a quick reconciliation pass.
+
+
+
+
From 3d3146986710f6680f8265bdf462f8779de30aa1 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 23 May 2026 17:19:01 -0400
Subject: [PATCH 06/45] update docs pages redirect target url
---
.beads/issues.jsonl | 2 +
.github/workflows/docs-pages.yml | 2 +-
...05-23-update-github-pages-docs-target.html | 133 ++++++++++++++++++
3 files changed, 136 insertions(+), 1 deletion(-)
create mode 100644 docs/turns/2026-05-23-update-github-pages-docs-target.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 01e0621..47f26a9 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -21,6 +21,8 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-t8b","title":"Update GitHub Pages docs URL target","description":"Adjust the docs Pages publish workflow so the deployed landing behavior explicitly targets dirtydishes.github.io/islandflow/docs and keeps the docs payload path consistent.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T21:18:04Z","created_by":"dirtydishes","updated_at":"2026-05-23T21:18:59Z","started_at":"2026-05-23T21:18:06Z","closed_at":"2026-05-23T21:18:59Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-kgu","title":"Reconcile PR #8 branch with current main","description":"Why this issue exists and what needs to be done: user requested reconciliation for PR #8. Identify the PR #8 branch, merge/rebase with current main, resolve conflicts, validate, and push the updated branch so the PR can merge cleanly.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T20:14:36Z","created_by":"dirtydishes","updated_at":"2026-05-23T20:24:29Z","started_at":"2026-05-23T20:14:39Z","closed_at":"2026-05-23T20:24:29Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-l9h","title":"stop persisting non-signal option prints in clickhouse","description":"Why: non-signal option prints are storage noise and should not be persisted by default.\\n\\nWhat: add OPTIONS_PERSIST_SIGNAL_ONLY env flag (default true), gate option_print inserts in ingest-options, add tests for persistence behavior, update env examples, and document one-off cleanup SQL for existing non-signal rows.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T03:02:32Z","created_by":"dirtydishes","updated_at":"2026-05-23T03:06:34Z","started_at":"2026-05-23T03:02:35Z","closed_at":"2026-05-23T03:06:34Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-2cj","title":"Add Forgejo-first agent workflow guidance to AGENTS.md","description":"Why this issue exists and what needs to be done:\\n- The repository’s canonical home is Forgejo at git.deltaisland.io, but AGENTS.md does not currently direct agents to prefer Forgejo-specific workflows.\\n- Update AGENTS.md so agents treat Forgejo as primary and use the fj CLI for pull request workflows.\\n- Keep existing Beads and completion instructions intact while clarifying remote preference and command usage.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-23T02:51:31Z","created_by":"dirtydishes","updated_at":"2026-05-23T02:55:42Z","closed_at":"2026-05-23T02:55:42Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-xc5","title":"One-time bidirectional git remote backfill between github and forgejo","description":"Perform a one-time sync so github and forgejo contain the same branch/tag refs and historical commits, including pre-transition github history and newer forgejo commits. Document exact commands and validation results.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-21T01:25:05Z","created_by":"dirtydishes","updated_at":"2026-05-21T01:26:19Z","started_at":"2026-05-21T01:25:16Z","closed_at":"2026-05-21T01:26:19Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml
index 9c4db98..5dd6927 100644
--- a/.github/workflows/docs-pages.yml
+++ b/.github/workflows/docs-pages.yml
@@ -36,7 +36,7 @@ jobs:
run: |
mkdir -p site/docs
cp -R docs/. site/docs/
- printf '%s\n' 'Islandflow DocsContinue to docs' > site/index.html
+ printf '%s\n' 'Islandflow DocsContinue to docs' > site/index.html
touch site/.nojekyll
- name: Upload Pages artifact
diff --git a/docs/turns/2026-05-23-update-github-pages-docs-target.html b/docs/turns/2026-05-23-update-github-pages-docs-target.html
new file mode 100644
index 0000000..842a0c3
--- /dev/null
+++ b/docs/turns/2026-05-23-update-github-pages-docs-target.html
@@ -0,0 +1,133 @@
+
+
+
+
+
+ Turn Report - Update GitHub Pages Docs Target
+
+
+
+
+
Update GitHub Pages docs target URL
+
Completed on May 23, 2026.
+
+
+
Summary
+
Updated the GitHub Pages workflow so the generated root landing page now redirects explicitly to https://dirtydishes.github.io/islandflow/docs/.
+
+
+
+
Changes Made
+
+
Edited .github/workflows/docs-pages.yml.
+
Changed the root site/index.html meta-refresh and fallback link from a relative ./docs/ target to an absolute Pages URL target.
+
+
+
+
+
Context
+
The existing docs Pages workflow already copied repository docs into site/docs/. The requested update was to ensure the published root route consistently forwards to the canonical project URL dirtydishes.github.io/islandflow/docs.
+
+
+
+
Important Implementation Details
+
+
Deployment artifact structure remains unchanged: docs are still published under site/docs/.
+
Only the redirect target changed, minimizing risk of deployment regressions.
+
Fallback anchor text now points to the same absolute URL as the redirect destination.
Visiting the Pages root now consistently routes users to /islandflow/docs/ on the canonical host.
+
Users get a stable docs destination regardless of relative path behavior.
+
+
+
+
+
Validation
+
+
Reviewed workflow diff to confirm only redirect target changed.
+
Attempted to run bunx actionlint .github/workflows/docs-pages.yml, but the package executable could not be resolved in this environment.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
Absolute URL redirect is now tied to the current canonical host/path; if Pages host naming changes, this line must be updated.
+
No runtime workflow execution was performed locally; final validation occurs on next GitHub Actions run.
+
+
+
+
+
Follow-up Work
+
+
Add actionlint as a repo-managed dev tool or CI check so workflow linting is repeatable in local and CI environments.
+
+
+
+
+
From aae3fa1f19d6e476c7fbff374bb31ae2194b9eea Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 23 May 2026 18:25:22 -0400
Subject: [PATCH 07/45] fix docs pages workflow for gh-pages branch deploy
---
.beads/issues.jsonl | 1 +
.github/workflows/docs-pages.yml | 44 +++++++++++++++-----------------
2 files changed, 22 insertions(+), 23 deletions(-)
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 47f26a9..82bff76 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,3 +1,4 @@
+{"_type":"issue","id":"islandflow-bc7","title":"Fix docs Pages workflow configure-pages failure","description":"Replace the current docs Pages deployment flow so workflow runs succeed even when configure-pages cannot read or enable the site. Keep published docs target behavior for dirtydishes.github.io/islandflow/docs.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T22:23:28Z","created_by":"dirtydishes","updated_at":"2026-05-23T22:25:19Z","started_at":"2026-05-23T22:23:31Z","closed_at":"2026-05-23T22:25:19Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-3o0","title":"address forgejo issue #10 security dependency cves","description":"Track remediation for Forgejo issue #10 (2026-05-23 security CVE triage): upgrade dependency chain to resolve tar/ws/postcss/tmp advisories, validate with bun audit and relevant quality gates.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T16:59:34Z","created_by":"dirtydishes","updated_at":"2026-05-23T17:03:06Z","started_at":"2026-05-23T16:59:38Z","closed_at":"2026-05-23T17:03:06Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-thp","title":"stabilize live api memory and reduce internal cache churn","description":"The native VPS deployment is repeatedly OOM-killing islandflow-api.service during live operation. The API live cache is retaining oversized channel histories and rewriting large Redis lists on every flush, which drives multi-GB Bun RSS and heavy loopback traffic between the API, Redis, NATS, and ClickHouse. Implement an emergency VPS mitigation plus repo hardening so unsafe env values, reconnect snapshots, and Redis persistence patterns cannot push the live API back into OOM.","acceptance_criteria":"1. VPS live cache env values are reduced to safe defaults and live redis state is cleared before restart. 2. services/api/src/live.ts enforces server-side live cache caps and clamps snapshot_limit accordingly. 3. Hot generic feed Redis persistence no longer rewrites entire lists on every flush. 4. Metrics/logging expose subscription counts, snapshot sizes, redis flush volume, and API memory trend. 5. Relevant tests pass and the deployment is restarted successfully.","notes":"Implemented and deployed the live-state hardening to the VPS. Final validation after restart showed the API around 120 MB RSS with capped live cache depths and clean systemd restarts.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T01:30:43Z","created_by":"dirtydishes","updated_at":"2026-05-23T01:50:41Z","started_at":"2026-05-23T01:30:52Z","closed_at":"2026-05-23T01:50:41Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-sc6","title":"fix electron codex bridge preload loading","description":"Electron settings showed the browser-only Desktop Required fallback because the renderer did not see the native islandflowDesktop preload bridge or an Electron user-agent marker. Fix the desktop launch path so ChatGPT/Codex subscription controls are available inside Islandflow Desktop again.","notes":"Reopened after live Electron still showed the browser-only fallback. Follow-up fix adds an explicit preload runtime marker and web runtime detection for that marker so Electron is recognized even when the bridge is not ready and the user agent lacks an Electron token.","status":"closed","priority":1,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-05-20T23:42:58Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:51:43Z","closed_at":"2026-05-20T23:51:43Z","close_reason":"Follow-up fix added an explicit islandflowDesktopRuntime preload marker and taught the web runtime to recognize that marker plus IslandflowDesktop user-agent tokens, so Electron no longer falls into the browser-only fallback when the AI bridge is delayed or unavailable. Desktop build and focused desktop/web tests pass; full web build still blocked by islandflow-c8f.","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml
index 5dd6927..bb72ee0 100644
--- a/.github/workflows/docs-pages.yml
+++ b/.github/workflows/docs-pages.yml
@@ -11,23 +11,18 @@ on:
workflow_dispatch:
permissions:
- contents: read
- pages: write
- id-token: write
+ contents: write
concurrency:
- group: "pages"
+ group: "docs-pages"
cancel-in-progress: true
jobs:
- build:
+ publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v4
-
- - name: Configure Pages
- uses: actions/configure-pages@v5
+ uses: actions/checkout@v5
- name: Build docs index
run: node scripts/generate-docs-index.mjs
@@ -39,18 +34,21 @@ jobs:
printf '%s\n' 'Islandflow DocsContinue to docs' > site/index.html
touch site/.nojekyll
- - name: Upload Pages artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: site
+ - name: Publish to gh-pages branch
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ set -euo pipefail
+ rm -rf .gh-pages-tmp
+ mkdir .gh-pages-tmp
+ cp -R site/. .gh-pages-tmp/
+ cd .gh-pages-tmp
- deploy:
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- needs: build
- runs-on: ubuntu-latest
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
+ git init
+ git checkout -b gh-pages
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git add -A
+ git commit -m "publish docs from ${GITHUB_SHA}"
+
+ git push --force "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" gh-pages:gh-pages
From 2e48283c5beb897607b69a90eda8fc4bb111a822 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 23 May 2026 18:28:24 -0400
Subject: [PATCH 08/45] sync github mirror for docs pages workflow fix
---
.beads/issues.jsonl | 1 +
1 file changed, 1 insertion(+)
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 82bff76..7e0ffe1 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,3 +1,4 @@
+{"_type":"issue","id":"islandflow-jad","title":"Sync docs pages workflow fix to github mirror","description":"GitHub is still running an older docs Pages workflow with configure-pages because github/main is behind forgejo/main. Push the already-fixed workflow commit to the GitHub mirror so Actions runs the gh-pages branch deployment flow instead.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T22:27:46Z","created_by":"dirtydishes","updated_at":"2026-05-23T22:28:24Z","started_at":"2026-05-23T22:28:10Z","closed_at":"2026-05-23T22:28:24Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-bc7","title":"Fix docs Pages workflow configure-pages failure","description":"Replace the current docs Pages deployment flow so workflow runs succeed even when configure-pages cannot read or enable the site. Keep published docs target behavior for dirtydishes.github.io/islandflow/docs.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T22:23:28Z","created_by":"dirtydishes","updated_at":"2026-05-23T22:25:19Z","started_at":"2026-05-23T22:23:31Z","closed_at":"2026-05-23T22:25:19Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-3o0","title":"address forgejo issue #10 security dependency cves","description":"Track remediation for Forgejo issue #10 (2026-05-23 security CVE triage): upgrade dependency chain to resolve tar/ws/postcss/tmp advisories, validate with bun audit and relevant quality gates.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T16:59:34Z","created_by":"dirtydishes","updated_at":"2026-05-23T17:03:06Z","started_at":"2026-05-23T16:59:38Z","closed_at":"2026-05-23T17:03:06Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-thp","title":"stabilize live api memory and reduce internal cache churn","description":"The native VPS deployment is repeatedly OOM-killing islandflow-api.service during live operation. The API live cache is retaining oversized channel histories and rewriting large Redis lists on every flush, which drives multi-GB Bun RSS and heavy loopback traffic between the API, Redis, NATS, and ClickHouse. Implement an emergency VPS mitigation plus repo hardening so unsafe env values, reconnect snapshots, and Redis persistence patterns cannot push the live API back into OOM.","acceptance_criteria":"1. VPS live cache env values are reduced to safe defaults and live redis state is cleared before restart. 2. services/api/src/live.ts enforces server-side live cache caps and clamps snapshot_limit accordingly. 3. Hot generic feed Redis persistence no longer rewrites entire lists on every flush. 4. Metrics/logging expose subscription counts, snapshot sizes, redis flush volume, and API memory trend. 5. Relevant tests pass and the deployment is restarted successfully.","notes":"Implemented and deployed the live-state hardening to the VPS. Final validation after restart showed the API around 120 MB RSS with capped live cache depths and clean systemd restarts.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T01:30:43Z","created_by":"dirtydishes","updated_at":"2026-05-23T01:50:41Z","started_at":"2026-05-23T01:30:52Z","closed_at":"2026-05-23T01:50:41Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
From 5ff2fa6d2cbec91b456750c60ec09380f4fe18b3 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 23 May 2026 18:45:52 -0400
Subject: [PATCH 09/45] turn doc instruction tuning
---
AGENTS.md | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/AGENTS.md b/AGENTS.md
index b97b7fd..7866794 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -90,6 +90,26 @@ At the end of every completed implementation task, before final handoff, create
This documentation is mandatory whenever code, configuration, tests, or project files were changed.
+### Do not produce this for minor or trivial changes, including but not limited to:
+
+- Syntax fixes
+- Code refactoring
+- Documentation updates
+- Reconciling PRs
+- Updating AGENTS.md or other documentation
+
+**Feel free to use your own judgement and always prompt the user if you are unsure if this change requires documentation or not.**
+
+### When making a minor update to a previous change, update the existing documentation instead of creating a new file. Use the following format:
+
+**"New Changes as of {time and date at which the change was made}"**
+- **Summary of changes**
+- **Why this change was made**
+- **Code diffs**
+- **Related issues or PRs**
+
+Additionally, add a note to each section explaining why the changes were made.
+
### Location
Save the document in:
From 4a0e9e7fe14f06051d0c41311def6cfb72fd8ee6 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 23 May 2026 18:52:15 -0400
Subject: [PATCH 10/45] default turn-doc diffs to @pierre/diffs and add
dependency
---
.beads/issues.jsonl | 1 +
AGENTS.md | 6 ++-
bun.lock | 101 ++++++++++++++++++++++++++++++++++++++++++++
package.json | 3 ++
4 files changed, 109 insertions(+), 2 deletions(-)
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 7e0ffe1..1b5d305 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -79,6 +79,7 @@
{"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-gm0","title":"Default turn-doc diffs to @pierre/diffs","description":"Why this issue exists and what needs to be done\\n\\nUpdate AGENTS.md turn-documentation guidance to prefer @pierre/diffs output with an explicit fallback path when unavailable, and include the related package manifest/lock updates in the same change set.","status":"in_progress","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T22:51:57Z","created_by":"dirtydishes","updated_at":"2026-05-23T22:52:00Z","started_at":"2026-05-23T22:52:00Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-hpf","title":"add anatomy explainer for options print and smart money flow","description":"Create a standalone docs/anatomy.html reference page that explains the end-to-end lifecycle of an options print through enrichment, signal filtering, compute clustering, flow packet creation, smart-money evaluation, classifier hits, alerts, and API/live consumption. The page should be polished, user-readable, and visually strong enough to serve as a reusable reference artifact for both technical and non-technical readers.","notes":"Added docs/anatomy.html as a standalone reference page for the options-print to smart-money pipeline, styled in the repo product register and layered for executive, mixed technical, and operator-level readers. Regenerated docs/index.html so the page is discoverable from the docs surface.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T02:18:48Z","created_by":"dirtydishes","updated_at":"2026-05-23T02:24:58Z","started_at":"2026-05-23T02:18:53Z","closed_at":"2026-05-23T02:24:58Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-4ca","title":"Publish May 21 standup git summary","description":"Create the daily standup-ready git activity summary for 2026-05-21, save the HTML artifact under docs/general, add the required turn document, and push the result so the automation leaves a durable record.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-22T13:03:00Z","created_by":"dirtydishes","updated_at":"2026-05-22T13:05:05Z","started_at":"2026-05-22T13:03:03Z","closed_at":"2026-05-22T13:05:05Z","close_reason":"Created the 2026-05-21 standup summary in docs/general, added the required turn document, and prepared the repo for commit/push.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-hgm","title":"Publish May 20 standup git summary","description":"Create the daily standup-ready git activity summary for 2026-05-20, save the HTML artifact under docs/general, and push the result so the automation leaves a durable record.","status":"closed","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-21T13:02:38Z","created_by":"dirtydishes","updated_at":"2026-05-21T13:05:16Z","closed_at":"2026-05-21T13:05:16Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/AGENTS.md b/AGENTS.md
index 7866794..84fe6f5 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -90,6 +90,8 @@ At the end of every completed implementation task, before final handoff, create
This documentation is mandatory whenever code, configuration, tests, or project files were changed.
+For diff content in turn documentation (including "Code diffs" and "Relevant Diff Snippets"), use `@pierre/diffs` output by default. If `@pierre/diffs` is unavailable because of a real tool or blocking error, use a clearly labeled plain diff/code block fallback and note why.
+
### Do not produce this for minor or trivial changes, including but not limited to:
- Syntax fixes
@@ -105,7 +107,7 @@ This documentation is mandatory whenever code, configuration, tests, or project
**"New Changes as of {time and date at which the change was made}"**
- **Summary of changes**
- **Why this change was made**
-- **Code diffs**
+- **Code diffs** (use `@pierre/diffs` output by default; if unavailable, include a clearly labeled plain diff/code block and note why)
- **Related issues or PRs**
Additionally, add a note to each section explaining why the changes were made.
@@ -154,7 +156,7 @@ Each turn document must include these sections:
2. **Changes Made**
3. **Context**
4. **Important Implementation Details**
-5. **Relevant Diff Snippets**
+5. **Relevant Diff Snippets** (render with `@pierre/diffs` output by default; if unavailable, include a clearly labeled plain diff/code block and note why)
6. **Expected Impact for End-Users**
7. **Validation**
8. **Issues, Limitations, and Mitigations**
diff --git a/bun.lock b/bun.lock
index 147e178..db93a84 100644
--- a/bun.lock
+++ b/bun.lock
@@ -4,6 +4,9 @@
"workspaces": {
"": {
"name": "islandflow",
+ "dependencies": {
+ "@pierre/diffs": "^1.2.2",
+ },
"devDependencies": {
"typescript-language-server": "^5.1.3",
},
@@ -381,6 +384,10 @@
"@npmcli/move-file": ["@npmcli/move-file@2.0.1", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ=="],
+ "@pierre/diffs": ["@pierre/diffs@1.2.2", "", { "dependencies": { "@pierre/theme": "1.0.3", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-MvWLv2oSOJOF8oYXWLdhicguHM11G/VNWu6OPR5ZETolp2NM2/KPQG3cZTnKpJ6ImqEHwvw6Gl6z2gmmy2FQmQ=="],
+
+ "@pierre/theme": ["@pierre/theme@1.0.3", "", {}, "sha512-sWHv11TMoqKxKDgTIk5VbhQjdPhs8DCcBxbjh3mRlS3YOM/OcrWoGX6MM8eBGn9cUu3M46Py0JnxsG2nJaFTuA=="],
+
"@redis/bloom": ["@redis/bloom@5.10.0", "", { "peerDependencies": { "@redis/client": "^5.10.0" } }, "sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A=="],
"@redis/client": ["@redis/client@5.10.0", "", { "dependencies": { "cluster-key-slot": "1.1.2" } }, "sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA=="],
@@ -391,6 +398,22 @@
"@redis/time-series": ["@redis/time-series@5.10.0", "", { "peerDependencies": { "@redis/client": "^5.10.0" } }, "sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg=="],
+ "@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="],
+
+ "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="],
+
+ "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g=="],
+
+ "@shikijs/langs": ["@shikijs/langs@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg=="],
+
+ "@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="],
+
+ "@shikijs/transformers": ["@shikijs/transformers@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/types": "3.23.0" } }, "sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ=="],
+
+ "@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="],
+
+ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
+
"@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
@@ -411,12 +434,16 @@
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
+ "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
+
"@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="],
+ "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
+
"@types/mute-stream": ["@types/mute-stream@0.0.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow=="],
"@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="],
@@ -427,10 +454,14 @@
"@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],
+ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
+
"@types/wrap-ansi": ["@types/wrap-ansi@3.0.0", "", {}, "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="],
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
+ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="],
+
"@vscode/sudo-prompt": ["@vscode/sudo-prompt@9.3.2", "", {}, "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw=="],
"@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="],
@@ -529,8 +560,14 @@
"caniuse-lite": ["caniuse-lite@1.0.30001792", "", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="],
+ "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
+
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+ "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
+
+ "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
+
"chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
@@ -563,6 +600,8 @@
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
+ "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
+
"commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
"compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="],
@@ -589,10 +628,16 @@
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
+ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
+
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="],
+ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
+
+ "diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
+
"dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
@@ -707,8 +752,14 @@
"hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="],
+ "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
+
+ "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
+
"hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="],
+ "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
+
"http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
"http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="],
@@ -801,18 +852,32 @@
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
+ "lru_map": ["lru_map@0.4.1", "", {}, "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg=="],
+
"make-fetch-happen": ["make-fetch-happen@10.2.1", "", { "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", "socks-proxy-agent": "^7.0.0", "ssri": "^9.0.0" } }, "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w=="],
"map-age-cleaner": ["map-age-cleaner@0.1.3", "", { "dependencies": { "p-defer": "^1.0.0" } }, "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w=="],
"matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="],
+ "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
+
"mem": ["mem@4.3.0", "", { "dependencies": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" } }, "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w=="],
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
+ "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
+
+ "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
+
+ "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
+
+ "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
+
+ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
+
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
@@ -881,6 +946,10 @@
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
+ "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="],
+
+ "oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="],
+
"ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
"p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
@@ -939,6 +1008,8 @@
"promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="],
+ "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
+
"pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
@@ -961,6 +1032,12 @@
"redis": ["redis@5.10.0", "", { "dependencies": { "@redis/bloom": "5.10.0", "@redis/client": "5.10.0", "@redis/json": "5.10.0", "@redis/search": "5.10.0", "@redis/time-series": "5.10.0" } }, "sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw=="],
+ "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
+
+ "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
+
+ "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="],
+
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
@@ -1007,6 +1084,8 @@
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+ "shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="],
+
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
@@ -1023,6 +1102,8 @@
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
+ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
+
"spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="],
"spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="],
@@ -1039,6 +1120,8 @@
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+ "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
+
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
@@ -1069,6 +1152,8 @@
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
+ "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
+
"trim-repeated": ["trim-repeated@1.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.2" } }, "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -1087,6 +1172,16 @@
"unique-slug": ["unique-slug@3.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w=="],
+ "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
+
+ "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
+
+ "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
+
+ "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
+
+ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
+
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
@@ -1097,6 +1192,10 @@
"validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="],
+ "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
+
+ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
+
"watchpack": ["watchpack@2.5.1", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg=="],
"wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
@@ -1135,6 +1234,8 @@
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
+
"@electron-forge/template-webpack-typescript/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="],
"@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="],
diff --git a/package.json b/package.json
index 8786c51..b83476b 100644
--- a/package.json
+++ b/package.json
@@ -31,5 +31,8 @@
"postcss": "^8.5.15",
"tar": "^7.5.15",
"tmp": "^0.2.5"
+ },
+ "dependencies": {
+ "@pierre/diffs": "^1.2.2"
}
}
From fda7d5f8fe45978b302660b74842630eba6704d4 Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 23 May 2026 18:53:58 -0400
Subject: [PATCH 11/45] add turn doc for pierre diffs policy update
---
.beads/issues.jsonl | 2 +-
.../2026-05-23-default-turn-doc-diffs.html | 148 ++++++++++++++++++
2 files changed, 149 insertions(+), 1 deletion(-)
create mode 100644 docs/turns/2026-05-23-default-turn-doc-diffs.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 1b5d305..283117b 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -79,7 +79,7 @@
{"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-gm0","title":"Default turn-doc diffs to @pierre/diffs","description":"Why this issue exists and what needs to be done\\n\\nUpdate AGENTS.md turn-documentation guidance to prefer @pierre/diffs output with an explicit fallback path when unavailable, and include the related package manifest/lock updates in the same change set.","status":"in_progress","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T22:51:57Z","created_by":"dirtydishes","updated_at":"2026-05-23T22:52:00Z","started_at":"2026-05-23T22:52:00Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-gm0","title":"Default turn-doc diffs to @pierre/diffs","description":"Why this issue exists and what needs to be done\\n\\nUpdate AGENTS.md turn-documentation guidance to prefer @pierre/diffs output with an explicit fallback path when unavailable, and include the related package manifest/lock updates in the same change set.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T22:51:57Z","created_by":"dirtydishes","updated_at":"2026-05-23T22:52:23Z","started_at":"2026-05-23T22:52:00Z","closed_at":"2026-05-23T22:52:23Z","close_reason":"completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-hpf","title":"add anatomy explainer for options print and smart money flow","description":"Create a standalone docs/anatomy.html reference page that explains the end-to-end lifecycle of an options print through enrichment, signal filtering, compute clustering, flow packet creation, smart-money evaluation, classifier hits, alerts, and API/live consumption. The page should be polished, user-readable, and visually strong enough to serve as a reusable reference artifact for both technical and non-technical readers.","notes":"Added docs/anatomy.html as a standalone reference page for the options-print to smart-money pipeline, styled in the repo product register and layered for executive, mixed technical, and operator-level readers. Regenerated docs/index.html so the page is discoverable from the docs surface.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T02:18:48Z","created_by":"dirtydishes","updated_at":"2026-05-23T02:24:58Z","started_at":"2026-05-23T02:18:53Z","closed_at":"2026-05-23T02:24:58Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-4ca","title":"Publish May 21 standup git summary","description":"Create the daily standup-ready git activity summary for 2026-05-21, save the HTML artifact under docs/general, add the required turn document, and push the result so the automation leaves a durable record.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-22T13:03:00Z","created_by":"dirtydishes","updated_at":"2026-05-22T13:05:05Z","started_at":"2026-05-22T13:03:03Z","closed_at":"2026-05-22T13:05:05Z","close_reason":"Created the 2026-05-21 standup summary in docs/general, added the required turn document, and prepared the repo for commit/push.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-hgm","title":"Publish May 20 standup git summary","description":"Create the daily standup-ready git activity summary for 2026-05-20, save the HTML artifact under docs/general, and push the result so the automation leaves a durable record.","status":"closed","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-21T13:02:38Z","created_by":"dirtydishes","updated_at":"2026-05-21T13:05:16Z","closed_at":"2026-05-21T13:05:16Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/docs/turns/2026-05-23-default-turn-doc-diffs.html b/docs/turns/2026-05-23-default-turn-doc-diffs.html
new file mode 100644
index 0000000..db01d0d
--- /dev/null
+++ b/docs/turns/2026-05-23-default-turn-doc-diffs.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+ Turn Report - Default Turn-Doc Diffs to @pierre/diffs
+
+
+
+
+
Default turn-doc diffs to @pierre/diffs
+
Completed on May 23, 2026 at 6:47 PM ET.
+
+
+
Summary
+
Updated repository turn-documentation rules to prefer @pierre/diffs for diff snippets, added a documented fallback path, and included the package/lock updates needed for consistent local usage.
+
+
+
+
Changes Made
+
+
Edited AGENTS.md to add a default diff-format policy for turn docs.
+
Updated the minor-update template bullet for Code diffs with explicit @pierre/diffs default plus fallback wording.
+
Updated required section Relevant Diff Snippets with the same default-and-fallback wording.
+
Added @pierre/diffs to root package.json dependencies and synced bun.lock.
+
+
+
+
+
Context
+
The existing guidance required a diff section but did not explicitly standardize on a single rendering tool. This change aligns turn-doc expectations around one default tool while preserving an escape hatch when tooling is unavailable.
+
+
+
+
Important Implementation Details
+
+
The policy is intentionally a preferred default, not a hard requirement.
+
Fallback usage is constrained to real tool/blocking errors and must be labeled with a reason.
+
No runtime application behavior was changed; this is workflow/documentation and dependency-surface work.
+
+
+
+
+
Relevant Diff Snippets
+
@pierre/diffs is now the repository default for this section.
+
--- AGENTS.md
++++ AGENTS.md
+@@
++For diff content in turn documentation (including "Code diffs" and "Relevant Diff Snippets"), use `@pierre/diffs` output by default.
+@@
+-- **Code diffs**
++- **Code diffs** (use `@pierre/diffs` output by default; if unavailable, include a clearly labeled plain diff/code block and note why)
+@@
+-5. **Relevant Diff Snippets**
++5. **Relevant Diff Snippets** (render with `@pierre/diffs` output by default; if unavailable, include a clearly labeled plain diff/code block and note why)
+
+--- package.json
++++ package.json
+@@
++ "dependencies": {
++ "@pierre/diffs": "^1.2.2"
++ }
+
+
+
+
Expected Impact for End-Users
+
+
Future turn docs should have more consistent and readable diff presentation.
+
Contributors have clearer guidance for fallback behavior when the preferred renderer cannot be used.
+
+
+
+
+
Validation
+
+
Verified policy text appears in all required AGENTS locations for diff guidance.
+
Ran bun install --frozen-lockfile and confirmed lockfile consistency with no changes.
+
Confirmed repository is clean and branch is up to date after push.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
This change standardizes policy but does not retroactively update old turn docs.
+
Actual visual rendering still depends on environment/tool availability; fallback instructions mitigate this.
+
+
+
+
+
Follow-up Work
+
+
Optionally add a tiny helper script/example to generate @pierre/diffs HTML snippets directly for turn docs.
+
+
+
+
+
From f056f6d2b8a5deb3b6686f3267a868c4da3c05bd Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 23 May 2026 19:02:18 -0400
Subject: [PATCH 12/45] clarify when turn docs are actually required
---
.beads/issues.jsonl | 1 +
AGENTS.md | 32 +++++++++++++++++++++++---------
2 files changed, 24 insertions(+), 9 deletions(-)
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 283117b..365ddaa 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -23,6 +23,7 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-hoh","title":"clarify turn-doc exemptions and ambiguity rule","description":"Update AGENTS.md turn documentation rules so minor/trivial checklist takes precedence, ambiguous cases require user check-in, and completion rule applies only when turn docs are required.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-23T23:02:10Z","created_by":"dirtydishes","updated_at":"2026-05-23T23:02:10Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-t8b","title":"Update GitHub Pages docs URL target","description":"Adjust the docs Pages publish workflow so the deployed landing behavior explicitly targets dirtydishes.github.io/islandflow/docs and keeps the docs payload path consistent.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T21:18:04Z","created_by":"dirtydishes","updated_at":"2026-05-23T21:18:59Z","started_at":"2026-05-23T21:18:06Z","closed_at":"2026-05-23T21:18:59Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-kgu","title":"Reconcile PR #8 branch with current main","description":"Why this issue exists and what needs to be done: user requested reconciliation for PR #8. Identify the PR #8 branch, merge/rebase with current main, resolve conflicts, validate, and push the updated branch so the PR can merge cleanly.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T20:14:36Z","created_by":"dirtydishes","updated_at":"2026-05-23T20:24:29Z","started_at":"2026-05-23T20:14:39Z","closed_at":"2026-05-23T20:24:29Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-l9h","title":"stop persisting non-signal option prints in clickhouse","description":"Why: non-signal option prints are storage noise and should not be persisted by default.\\n\\nWhat: add OPTIONS_PERSIST_SIGNAL_ONLY env flag (default true), gate option_print inserts in ingest-options, add tests for persistence behavior, update env examples, and document one-off cleanup SQL for existing non-signal rows.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T03:02:32Z","created_by":"dirtydishes","updated_at":"2026-05-23T03:06:34Z","started_at":"2026-05-23T03:02:35Z","closed_at":"2026-05-23T03:06:34Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/AGENTS.md b/AGENTS.md
index 84fe6f5..9a0234c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -90,17 +90,31 @@ At the end of every completed implementation task, before final handoff, create
This documentation is mandatory whenever code, configuration, tests, or project files were changed.
+### Precedence and classification
+
+Use this decision order before creating a turn document:
+
+1. Check the minor/trivial exemption checklist below first.
+2. If the task clearly matches an exemption, do not create a turn document.
+3. If the task is a clearly substantive implementation change, create a turn document.
+4. If classification is ambiguous or mixed, ask the user before creating a turn document.
+
+The minor/trivial exemptions override the general mandatory turn-document rule.
+
For diff content in turn documentation (including "Code diffs" and "Relevant Diff Snippets"), use `@pierre/diffs` output by default. If `@pierre/diffs` is unavailable because of a real tool or blocking error, use a clearly labeled plain diff/code block fallback and note why.
-### Do not produce this for minor or trivial changes, including but not limited to:
+### No turn document for minor/trivial checklist matches
-- Syntax fixes
-- Code refactoring
-- Documentation updates
-- Reconciling PRs
-- Updating AGENTS.md or other documentation
+Do not create a turn document when the change is minor/trivial and cleanly matches one of these categories:
-**Feel free to use your own judgement and always prompt the user if you are unsure if this change requires documentation or not.**
+- `AGENTS.md` changes or other documentation-only changes
+- Syntax-only fixes
+- Refactor-only changes with no behavior change
+- PR/conflict reconciliation work
+- Issue-tracker-only updates such as `beads/issues.json`
+- Support-file changes that only accompany one of the exempt categories above (for example lockfile or manifest updates required for docs-workflow changes)
+
+If a change does not cleanly fit either exempt or substantive buckets, ask the user before creating a turn document.
### When making a minor update to a previous change, update the existing documentation instead of creating a new file. Use the following format:
@@ -164,7 +178,7 @@ Each turn document must include these sections:
### Completion Rule
-A task is not complete until:
+A task that requires a turn document is not complete until:
1. The Beads workflow is updated
2. The turn document is created in `docs/turns`
@@ -174,7 +188,7 @@ A task is not complete until:
6. `git push forgejo ` succeeds
7. `git status` shows the branch is up to date with `forgejo/`
-For trivial changes, the document may be brief, but it must still exist and clearly explain what changed and how it was validated.
+For tasks that do require turn documentation, the document may be brief when scope is small, but it must clearly explain what changed and how it was validated.
## Plan Mode Documentation
From 7ca0e05a2dd00559bc4b639ba40d39d6900f7c8e Mon Sep 17 00:00:00 2001
From: dirtydishes
Date: Sat, 23 May 2026 19:39:19 -0400
Subject: [PATCH 13/45] rename tape to options and switch the web shell to a
drawer
---
.beads/issues.jsonl | 4 +-
apps/desktop/README.md | 2 +-
apps/desktop/src/security.test.ts | 9 +-
apps/web/app/globals.css | 175 +++--
apps/web/app/options/page.tsx | 7 +
apps/web/app/routes.test.ts | 6 +
apps/web/app/tape/page.tsx | 4 +-
apps/web/app/terminal.test.ts | 54 +-
apps/web/app/terminal.tsx | 155 ++++-
...2026-05-23-rename-tape-options-drawer.html | 654 ++++++++++++++++++
10 files changed, 916 insertions(+), 154 deletions(-)
create mode 100644 apps/web/app/options/page.tsx
create mode 100644 docs/turns/2026-05-23-rename-tape-options-drawer.html
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 365ddaa..7a0fe2d 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -23,7 +23,8 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"islandflow-hoh","title":"clarify turn-doc exemptions and ambiguity rule","description":"Update AGENTS.md turn documentation rules so minor/trivial checklist takes precedence, ambiguous cases require user check-in, and completion rule applies only when turn docs are required.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-23T23:02:10Z","created_by":"dirtydishes","updated_at":"2026-05-23T23:02:10Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-7ez","title":"rename tape to options and replace web rail with drawer shell","description":"Implement the web and desktop route transition from /tape to /options, keep /tape as a compatibility redirect, replace the persistent web rail with a shared sticky header plus overlay drawer, and update validation/docs to match.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T23:30:06Z","created_by":"dirtydishes","updated_at":"2026-05-23T23:38:59Z","started_at":"2026-05-23T23:30:24Z","closed_at":"2026-05-23T23:38:59Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-hoh","title":"clarify turn-doc exemptions and ambiguity rule","description":"Update AGENTS.md turn documentation rules so minor/trivial checklist takes precedence, ambiguous cases require user check-in, and completion rule applies only when turn docs are required.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-23T23:02:10Z","created_by":"dirtydishes","updated_at":"2026-05-23T23:02:30Z","closed_at":"2026-05-23T23:02:30Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-t8b","title":"Update GitHub Pages docs URL target","description":"Adjust the docs Pages publish workflow so the deployed landing behavior explicitly targets dirtydishes.github.io/islandflow/docs and keeps the docs payload path consistent.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T21:18:04Z","created_by":"dirtydishes","updated_at":"2026-05-23T21:18:59Z","started_at":"2026-05-23T21:18:06Z","closed_at":"2026-05-23T21:18:59Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-kgu","title":"Reconcile PR #8 branch with current main","description":"Why this issue exists and what needs to be done: user requested reconciliation for PR #8. Identify the PR #8 branch, merge/rebase with current main, resolve conflicts, validate, and push the updated branch so the PR can merge cleanly.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T20:14:36Z","created_by":"dirtydishes","updated_at":"2026-05-23T20:24:29Z","started_at":"2026-05-23T20:14:39Z","closed_at":"2026-05-23T20:24:29Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-l9h","title":"stop persisting non-signal option prints in clickhouse","description":"Why: non-signal option prints are storage noise and should not be persisted by default.\\n\\nWhat: add OPTIONS_PERSIST_SIGNAL_ONLY env flag (default true), gate option_print inserts in ingest-options, add tests for persistence behavior, update env examples, and document one-off cleanup SQL for existing non-signal rows.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T03:02:32Z","created_by":"dirtydishes","updated_at":"2026-05-23T03:06:34Z","started_at":"2026-05-23T03:02:35Z","closed_at":"2026-05-23T03:06:34Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
@@ -80,6 +81,7 @@
{"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"auto-import","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"islandflow-3by","title":"add interaction coverage for terminal navigation drawer","description":"Add browser- or DOM-level coverage for the shared terminal header drawer so open/close behavior, Escape dismissal, backdrop dismissal, and route-change dismissal are exercised beyond pure route helper tests.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-23T23:35:57Z","created_by":"dirtydishes","updated_at":"2026-05-23T23:35:57Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-gm0","title":"Default turn-doc diffs to @pierre/diffs","description":"Why this issue exists and what needs to be done\\n\\nUpdate AGENTS.md turn-documentation guidance to prefer @pierre/diffs output with an explicit fallback path when unavailable, and include the related package manifest/lock updates in the same change set.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T22:51:57Z","created_by":"dirtydishes","updated_at":"2026-05-23T22:52:23Z","started_at":"2026-05-23T22:52:00Z","closed_at":"2026-05-23T22:52:23Z","close_reason":"completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-hpf","title":"add anatomy explainer for options print and smart money flow","description":"Create a standalone docs/anatomy.html reference page that explains the end-to-end lifecycle of an options print through enrichment, signal filtering, compute clustering, flow packet creation, smart-money evaluation, classifier hits, alerts, and API/live consumption. The page should be polished, user-readable, and visually strong enough to serve as a reusable reference artifact for both technical and non-technical readers.","notes":"Added docs/anatomy.html as a standalone reference page for the options-print to smart-money pipeline, styled in the repo product register and layered for executive, mixed technical, and operator-level readers. Regenerated docs/index.html so the page is discoverable from the docs surface.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-23T02:18:48Z","created_by":"dirtydishes","updated_at":"2026-05-23T02:24:58Z","started_at":"2026-05-23T02:18:53Z","closed_at":"2026-05-23T02:24:58Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-4ca","title":"Publish May 21 standup git summary","description":"Create the daily standup-ready git activity summary for 2026-05-21, save the HTML artifact under docs/general, add the required turn document, and push the result so the automation leaves a durable record.","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-22T13:03:00Z","created_by":"dirtydishes","updated_at":"2026-05-22T13:05:05Z","started_at":"2026-05-22T13:03:03Z","closed_at":"2026-05-22T13:05:05Z","close_reason":"Created the 2026-05-21 standup summary in docs/general, added the required turn document, and prepared the repo for commit/push.","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/apps/desktop/README.md b/apps/desktop/README.md
index 9781c00..d8166b8 100644
--- a/apps/desktop/README.md
+++ b/apps/desktop/README.md
@@ -24,6 +24,6 @@ This workspace packages a thin Electron shell around the hosted Islandflow app.
## Development Notes
-- `ISLANDFLOW_DESKTOP_START_URL` controls which trusted app URL Electron loads.
+- `ISLANDFLOW_DESKTOP_START_URL` controls which trusted app URL Electron loads. Prefer `/options` for deep links; `/tape` remains supported and redirects in the web app for compatibility.
- `NEXT_PUBLIC_API_URL` remains a web-app setting and should typically be `https://flow.deltaisland.io` when developing the local UI inside Electron.
- `assets/` currently contains placeholders only; a real `.icns` icon is deferred.
diff --git a/apps/desktop/src/security.test.ts b/apps/desktop/src/security.test.ts
index 3fe3e23..dacabcb 100644
--- a/apps/desktop/src/security.test.ts
+++ b/apps/desktop/src/security.test.ts
@@ -8,7 +8,11 @@ import {
} from "./security.js";
describe("desktop URL policy", () => {
- it("allows the hosted production origin", () => {
+ it("allows the hosted production origin on /options", () => {
+ expect(isTrustedAppUrl("https://flow.deltaisland.io/options?symbol=SPY")).toBe(true);
+ });
+
+ it("keeps /tape trusted as a compatibility path on the same origin", () => {
expect(isTrustedAppUrl("https://flow.deltaisland.io/tape?symbol=SPY")).toBe(true);
});
@@ -37,5 +41,8 @@ describe("desktop URL policy", () => {
expect(resolveDesktopStartUrl(undefined)).toBe(DESKTOP_PRODUCTION_URL);
expect(resolveDesktopStartUrl("https://example.com")).toBe(DESKTOP_PRODUCTION_URL);
expect(resolveDesktopStartUrl("http://127.0.0.1:3000")).toBe("http://127.0.0.1:3000");
+ expect(resolveDesktopStartUrl("https://flow.deltaisland.io/options")).toBe(
+ "https://flow.deltaisland.io/options"
+ );
});
});
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css
index cf6746b..8c449c1 100644
--- a/apps/web/app/globals.css
+++ b/apps/web/app/globals.css
@@ -18,7 +18,7 @@
--red-soft: oklch(0.68 0.16 28 / 0.12);
--blue: oklch(0.72 0.13 247);
--blue-soft: oklch(0.72 0.13 247 / 0.11);
- --rail-width: 236px;
+ --drawer-width: min(320px, calc(100vw - 28px));
--topbar-height: 64px;
}
@@ -86,22 +86,43 @@ input {
}
.terminal-shell {
+ position: relative;
min-height: 100vh;
- display: grid;
- grid-template-columns: var(--rail-width) minmax(0, 1fr);
background: linear-gradient(180deg, oklch(0.14 0.011 250) 0%, oklch(0.11 0.01 250) 100%);
}
-.terminal-rail {
- position: sticky;
- top: 0;
- height: 100vh;
- padding: 22px 18px;
+.terminal-nav-drawer {
+ position: fixed;
+ inset: 0 auto 0 0;
+ z-index: 45;
+ width: var(--drawer-width);
+ padding: 20px 18px 18px;
display: flex;
flex-direction: column;
gap: 20px;
background: linear-gradient(180deg, oklch(0.16 0.012 250 / 0.98), oklch(0.13 0.011 250 / 0.98));
border-right: 1px solid var(--border);
+ box-shadow: 0 28px 72px rgba(0, 0, 0, 0.48);
+}
+
+.terminal-drawer-backdrop {
+ position: fixed;
+ inset: 0;
+ z-index: 40;
+ border: 0;
+ background: rgba(3, 5, 8, 0.62);
+ cursor: pointer;
+}
+
+.terminal-drawer-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.terminal-drawer-close {
+ flex: 0 0 auto;
}
.terminal-brand {
@@ -198,6 +219,7 @@ input {
.terminal-frame {
min-width: 0;
+ min-height: 100vh;
display: grid;
grid-template-rows: minmax(var(--topbar-height), auto) minmax(0, 1fr);
}
@@ -208,11 +230,39 @@ input {
z-index: 20;
display: flex;
align-items: center;
- justify-content: flex-end;
- gap: 12px;
+ justify-content: space-between;
+ gap: 16px;
padding: 10px 20px;
background: oklch(0.15 0.012 250 / 0.96);
border-bottom: 1px solid var(--border);
+ backdrop-filter: blur(12px);
+}
+
+.terminal-topbar-leading {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex: 0 0 auto;
+}
+
+.terminal-menu-trigger {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 104px;
+}
+
+.terminal-menu-trigger-icon {
+ display: inline-grid;
+ gap: 4px;
+}
+
+.terminal-menu-trigger-icon span {
+ display: block;
+ width: 14px;
+ height: 1px;
+ border-radius: 999px;
+ background: currentColor;
}
.status-dot,
@@ -463,7 +513,7 @@ input {
.terminal-content {
min-width: 0;
- padding: 24px 24px 24px;
+ padding: 24px clamp(16px, 2vw, 28px) 24px;
}
.page-shell {
@@ -689,8 +739,8 @@ h3 {
grid-template-columns: minmax(0, 2fr) minmax(320px, 1fr);
}
-.page-grid-tape {
- grid-template-columns: minmax(0, 1.5fr) minmax(320px, 1fr);
+.page-grid-options {
+ grid-template-columns: minmax(0, 1fr);
}
.page-grid-signals {
@@ -714,7 +764,7 @@ h3 {
.page-grid-home > :nth-child(3),
.page-grid-home > :nth-child(4),
-.page-grid-tape > :nth-child(1),
+.page-grid-options > :nth-child(1),
.page-grid-replay > :nth-child(1) {
grid-column: 1 / -1;
}
@@ -963,11 +1013,11 @@ h3 {
grid-row: 2;
}
-.page-grid-tape > :first-child {
+.page-grid-options > :first-child {
height: clamp(460px, 64vh, 880px);
}
-.page-grid-tape > :not(:first-child) {
+.page-grid-options > :not(:first-child) {
height: clamp(400px, 50vh, 680px);
}
@@ -1965,68 +2015,23 @@ h3 {
}
@media (max-width: 1180px) {
- .terminal-shell {
- grid-template-columns: 1fr;
- }
-
- .terminal-rail {
- position: sticky;
- top: 0;
- z-index: 35;
- height: auto;
- display: grid;
- grid-template-columns: minmax(170px, auto) minmax(0, 1fr);
- align-items: center;
- gap: 14px 18px;
- padding: 14px 16px;
- border-right: 0;
- border-bottom: 1px solid var(--border);
- }
-
- .terminal-brand {
- gap: 2px;
+ .terminal-nav-drawer {
+ width: min(300px, calc(100vw - 24px));
}
.terminal-brand-name {
font-size: 1.25rem;
}
- .terminal-nav {
- display: flex;
- min-width: 0;
- gap: 8px;
- overflow-x: auto;
- scrollbar-width: thin;
- }
-
- .terminal-nav-link {
- flex: 0 0 auto;
- white-space: nowrap;
- }
-
- .shell-metrics {
- grid-column: 1 / -1;
- margin-top: 0;
- grid-template-columns: repeat(4, minmax(136px, 1fr));
- gap: 8px;
- overflow-x: auto;
- padding-bottom: 2px;
- scrollbar-width: thin;
- }
-
.shell-metric {
min-width: 136px;
padding: 10px 12px;
}
-
- .terminal-topbar {
- position: static;
- }
}
@media (max-width: 980px) {
.page-grid-home,
- .page-grid-tape,
+ .page-grid-options,
.page-grid-signals,
.page-grid-charts,
.page-grid-replay,
@@ -2037,7 +2042,7 @@ h3 {
.page-grid-home > :nth-child(3),
.page-grid-home > :nth-child(4),
- .page-grid-tape > :nth-child(1),
+ .page-grid-options > :nth-child(1),
.page-grid-replay > :nth-child(1) {
grid-column: auto;
grid-row: auto;
@@ -2049,8 +2054,8 @@ h3 {
.page-grid-home > :nth-child(4),
.page-grid-signals > .terminal-pane,
.page-grid-replay > :not(:first-child),
- .page-grid-tape > :first-child,
- .page-grid-tape > :not(:first-child),
+ .page-grid-options > :first-child,
+ .page-grid-options > :not(:first-child),
.page-grid-charts > :last-child {
height: auto;
}
@@ -2062,14 +2067,12 @@ h3 {
.terminal-topbar {
align-items: center;
- justify-content: flex-end;
+ justify-content: space-between;
padding: 10px 16px;
}
.terminal-topbar-actions {
justify-content: flex-end;
- margin-left: auto;
- width: auto;
}
.terminal-topbar-controls {
@@ -2086,11 +2089,9 @@ h3 {
background-size: 24px 24px, 24px 24px, 100% 100%, auto;
}
- .terminal-rail {
- position: static;
- grid-template-columns: minmax(0, 1fr);
- gap: 12px;
- padding: 12px;
+ .terminal-nav-drawer {
+ width: min(340px, calc(100vw - 12px));
+ padding: 16px 12px 12px;
}
.terminal-brand {
@@ -2111,20 +2112,6 @@ h3 {
padding-bottom: 2px;
}
- .terminal-nav-link {
- padding: 12px;
- font-size: 0.72rem;
- }
-
- .shell-metrics {
- display: flex;
- gap: 8px;
- }
-
- .shell-metric {
- flex: 0 0 156px;
- }
-
.terminal-content {
padding: 16px 10px 22px;
}
@@ -2160,6 +2147,10 @@ h3 {
padding: 12px 10px;
}
+ .terminal-topbar-leading {
+ width: 100%;
+ }
+
.terminal-button,
.mode-button,
.filter-clear,
@@ -2186,8 +2177,14 @@ h3 {
align-items: stretch;
}
+ .terminal-menu-trigger {
+ width: 100%;
+ justify-content: center;
+ }
+
.terminal-topbar-mode .terminal-button,
.terminal-topbar-controls > .terminal-button,
+ .terminal-topbar-leading > .terminal-button,
.page-actions > .terminal-button,
.page-actions > .flow-filter-popover {
width: 100%;
diff --git a/apps/web/app/options/page.tsx b/apps/web/app/options/page.tsx
new file mode 100644
index 0000000..abfa3fa
--- /dev/null
+++ b/apps/web/app/options/page.tsx
@@ -0,0 +1,7 @@
+import { OptionsRoute } from "../terminal";
+
+export const dynamic = "force-dynamic";
+
+export default function Page() {
+ return ;
+}
diff --git a/apps/web/app/routes.test.ts b/apps/web/app/routes.test.ts
index 55b29e0..e217748 100644
--- a/apps/web/app/routes.test.ts
+++ b/apps/web/app/routes.test.ts
@@ -28,4 +28,10 @@ describe("legacy page redirects", () => {
expect(() => mod.default()).toThrow("NEXT_REDIRECT:/");
expect(redirect).toHaveBeenCalledWith("/");
});
+
+ it("redirects /tape to /options", async () => {
+ const mod = await import("./tape/page");
+ expect(() => mod.default()).toThrow("NEXT_REDIRECT:/options");
+ expect(redirect).toHaveBeenCalledWith("/options");
+ });
});
diff --git a/apps/web/app/tape/page.tsx b/apps/web/app/tape/page.tsx
index a692698..0c82e4a 100644
--- a/apps/web/app/tape/page.tsx
+++ b/apps/web/app/tape/page.tsx
@@ -1,7 +1,7 @@
-import { TapeRoute } from "../terminal";
+import { redirect } from "next/navigation";
export const dynamic = "force-dynamic";
export default function Page() {
- return ;
+ redirect("/options");
}
diff --git a/apps/web/app/terminal.test.ts b/apps/web/app/terminal.test.ts
index 92a9904..eb666c4 100644
--- a/apps/web/app/terminal.test.ts
+++ b/apps/web/app/terminal.test.ts
@@ -24,6 +24,7 @@ import {
getOptionScope,
getLiveFeedStatus,
getLiveManifest,
+ getTerminalNavCurrentHref,
getRouteFeatures,
getTapeVirtualConfig,
mergeHeldTapeHistory,
@@ -44,6 +45,7 @@ import {
smartMoneyProfileLabel,
smartMoneyToneForProfile,
getAlertFlowPacketRefs,
+ normalizeTerminalPathname,
resolveAlertFlowPacket,
statusLabel,
toggleFilterValue
@@ -165,18 +167,24 @@ describe("alert context hydration helpers", () => {
});
describe("live manifest", () => {
- it("includes only tape channels on /tape", () => {
+ it("includes only options channels on /options", () => {
const filters = buildDefaultFlowFilters();
- const channels = getLiveManifest("/tape", "SPY", 60000, filters).map(
+ const channels = getLiveManifest("/options", "SPY", 60000, filters).map(
(subscription) => subscription.channel
);
- expect(channels).toEqual(["options", "nbbo", "equities", "flow"]);
+ expect(channels).toEqual(["options", "nbbo", "flow"]);
});
- it("dedupes tape options subscription", () => {
+ it("keeps /tape as a compatibility alias for /options subscriptions", () => {
+ expect(getLiveManifest("/tape", "SPY", 60000, buildDefaultFlowFilters())).toEqual(
+ getLiveManifest("/options", "SPY", 60000, buildDefaultFlowFilters())
+ );
+ });
+
+ it("dedupes options subscriptions on /options", () => {
const tapeOptionsSubscriptions = getLiveManifest(
- "/tape",
+ "/options",
"SPY",
60000,
buildDefaultFlowFilters()
@@ -184,35 +192,35 @@ describe("live manifest", () => {
expect(tapeOptionsSubscriptions).toHaveLength(1);
});
- it("keeps option filters on /tape options subscriptions", () => {
+ it("keeps option filters on /options subscriptions", () => {
const filters = {
...buildDefaultFlowFilters(),
minNotional: 125_000
};
- const tapeOptionsSubscription = getLiveManifest("/tape", "SPY", 60000, filters).find(
+ const tapeOptionsSubscription = getLiveManifest("/options", "SPY", 60000, filters).find(
(subscription) => subscription.channel === "options"
);
expect(tapeOptionsSubscription?.filters).toBe(filters);
});
- it("applies global flow filters to flow subscriptions on /tape", () => {
+ it("applies global flow filters to flow subscriptions on /options", () => {
const filters = {
...buildDefaultFlowFilters(),
minNotional: 50_000
};
- const tapeFlowSubscription = getLiveManifest("/tape", "SPY", 60000, filters).find(
+ const tapeFlowSubscription = getLiveManifest("/options", "SPY", 60000, filters).find(
(subscription) => subscription.channel === "flow"
);
expect(tapeFlowSubscription?.filters).toBe(filters);
});
- it("includes scoped option and equity subscriptions", () => {
+ it("includes scoped option subscriptions on /options", () => {
const manifest = getLiveManifest(
- "/tape",
+ "/options",
"AAPL",
60000,
buildDefaultFlowFilters(),
@@ -226,15 +234,11 @@ describe("live manifest", () => {
(subscription): subscription is Extract<(typeof manifest)[number], { channel: "options" }> =>
subscription.channel === "options"
);
- const equitiesSubscription = manifest.find(
- (subscription): subscription is Extract<(typeof manifest)[number], { channel: "equities" }> =>
- subscription.channel === "equities"
- );
expect(optionsSubscription?.underlying_ids).toEqual(["AAPL"]);
expect(optionsSubscription?.option_contract_id).toBe("AAPL-2025-01-17-200-C");
expect(optionsSubscription?.snapshot_limit).toBe(100);
- expect(equitiesSubscription?.underlying_ids).toEqual(["AAPL"]);
+ expect(manifest.some((subscription) => subscription.channel === "equities")).toBe(false);
});
it("drops option-print filters for contract-focused options subscriptions but keeps flow filters", () => {
@@ -244,7 +248,7 @@ describe("live manifest", () => {
optionTypes: ["put"] as const
};
const manifest = getLiveManifest(
- "/tape",
+ "/options",
"AAPL",
60000,
filters,
@@ -443,15 +447,21 @@ describe("contract-focused option helpers", () => {
});
describe("route feature map", () => {
- it("maps /tape to tape panes and dependencies", () => {
- const features = getRouteFeatures("/tape");
+ it("maps /options to the options and packets panes", () => {
+ const features = getRouteFeatures("/options");
expect(features.showOptionsPane).toBe(true);
- expect(features.showEquitiesPane).toBe(true);
+ expect(features.showEquitiesPane).toBe(false);
expect(features.showFlowPane).toBe(true);
expect(features.needsClassifierDecor).toBe(true);
expect(features.alerts).toBe(false);
});
+ it("keeps /tape route compatibility while normalizing to /options", () => {
+ expect(normalizeTerminalPathname("/tape")).toBe("/options");
+ expect(getTerminalNavCurrentHref("/tape")).toBe("/options");
+ expect(getRouteFeatures("/tape")).toEqual(getRouteFeatures("/options"));
+ });
+
it("maps /signals to signal panes and dependencies", () => {
const features = getRouteFeatures("/signals");
expect(features.showAlertsPane).toBe(true);
@@ -506,10 +516,10 @@ describe("dark underlying route dependency helper", () => {
});
describe("terminal navigation", () => {
- it("exposes Home, Tape, and News as top-level destinations", () => {
+ it("exposes Home, Options, and News as top-level destinations", () => {
expect(NAV_ITEMS).toEqual([
{ href: "/", label: "Home" },
- { href: "/tape", label: "Tape" },
+ { href: "/options", label: "Options" },
{ href: "/news", label: "News" }
]);
});
diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx
index 3057f58..3444320 100644
--- a/apps/web/app/terminal.tsx
+++ b/apps/web/app/terminal.tsx
@@ -186,23 +186,34 @@ export const shouldIncludeEquitiesForDarkUnderlyingFallback = (): boolean => {
return false;
};
+const CANONICAL_OPTIONS_PATH = "/options";
+const TAPE_COMPAT_PATH = "/tape";
+const KNOWN_TERMINAL_PATHS = new Set([
+ CANONICAL_OPTIONS_PATH,
+ TAPE_COMPAT_PATH,
+ "/news",
+ "/signals",
+ "/charts",
+ "/replay"
+]);
+
+export const normalizeTerminalPathname = (pathname: string): string => {
+ if (pathname === TAPE_COMPAT_PATH) {
+ return CANONICAL_OPTIONS_PATH;
+ }
+ return KNOWN_TERMINAL_PATHS.has(pathname) ? pathname : "/";
+};
+
export const getRouteFeatures = (pathname: string): RouteFeatures => {
const includeEquitiesFallback = shouldIncludeEquitiesForDarkUnderlyingFallback();
- const normalizedPath =
- pathname === "/tape" ||
- pathname === "/news" ||
- pathname === "/signals" ||
- pathname === "/charts" ||
- pathname === "/replay"
- ? pathname
- : "/";
+ const normalizedPath = normalizeTerminalPathname(pathname);
switch (normalizedPath) {
- case "/tape":
+ case "/options":
return {
options: true,
nbbo: true,
- equities: true,
+ equities: false,
flow: true,
news: false,
alerts: false,
@@ -213,7 +224,7 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
equityCandles: false,
equityOverlay: false,
showOptionsPane: true,
- showEquitiesPane: true,
+ showEquitiesPane: false,
showFlowPane: true,
showNewsPane: false,
showAlertsPane: false,
@@ -370,6 +381,10 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
}
};
+export const getTerminalNavCurrentHref = (pathname: string): string => {
+ return normalizeTerminalPathname(pathname);
+};
+
const EMPTY_ALERT_EVENTS: AlertEvent[] = [];
const EMPTY_CLASSIFIER_HIT_EVENTS: ClassifierHitEvent[] = [];
const EMPTY_SMART_MONEY_EVENTS: SmartMoneyEvent[] = [];
@@ -7170,7 +7185,7 @@ const useTerminal = (): TerminalState => {
export const NAV_ITEMS = [
{ href: "/", label: "Home" },
- { href: "/tape", label: "Tape" },
+ { href: "/options", label: "Options" },
{ href: "/news", label: "News" }
] as const;
@@ -8812,8 +8827,31 @@ function SyntheticControlDock() {
export function TerminalAppShell({ children }: { children: ReactNode }) {
const state = useTerminalState();
const pathname = usePathname();
+ const [drawerOpen, setDrawerOpen] = useState(false);
const tickerFieldId = useId();
const tickerHintId = useId();
+ const activeNavHref = getTerminalNavCurrentHref(pathname);
+
+ useEffect(() => {
+ setDrawerOpen(false);
+ }, [pathname]);
+
+ useEffect(() => {
+ if (!drawerOpen) {
+ return;
+ }
+
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Escape") {
+ setDrawerOpen(false);
+ }
+ };
+
+ document.addEventListener("keydown", handleKeyDown);
+ return () => {
+ document.removeEventListener("keydown", handleKeyDown);
+ };
+ }, [drawerOpen]);
return (
@@ -8821,31 +8859,26 @@ export function TerminalAppShell({ children }: { children: ReactNode }) {
Skip to terminal content
-
Initialized Impeccable live-mode configuration for the Next.js app router so future visual iteration can inject the picker through apps/web/app/layout.tsx without a first-time setup detour.
+
+
+
+
+
Summary
+
The repository already had PRODUCT.md and DESIGN.md, so initialization preserved the existing Islandflow design context and added the missing live-mode configuration.
+
+
+
+
Changes Made
+
+
Added .impeccable/live/config.json for a Next.js App Router project.
+
Configured live injection to target apps/web/app/layout.tsx before </body> using JSX comment syntax.
+
Marked CSP as checked after the detector reported no Content Security Policy to patch.
+
Ignored the local runtime file .impeccable/live/server.json, which is regenerated when the live helper starts.
+
Created and claimed Beads issue islandflow-ep2 for the setup work.
+
+
+
+
+
Context
+
The Impeccable setup flow found existing product and design documents. The project register is product, and the UI conventions are already established around a dark evidence-console interface with amber as a sparse action and attention signal.
+
+
+
+
Important Implementation Details
+
+
The app uses the Next.js App Router, so the canonical live target is apps/web/app/layout.tsx.
+
The app shell already loads Quantico, IBM Plex Sans, and IBM Plex Mono, matching the documented Islandflow Terminal design system.
+
CSP detection returned {"shape": null, "signals": []}, so no source-level CSP patch was needed.
+
Running live.mjs creates .impeccable/live/server.json locally and temporarily injects a live script marker into layout.tsx; the source marker was removed after validation because the committed setup should stay configuration-only.
+
+
+
+
+
Relevant Diff Snippets
+
@pierre/diffs could not be run in this environment because the package did not expose a detectable executable, so this section uses a plain labeled diff fallback.
This does not change the Islandflow web UI for normal users. It improves the design workflow for contributors by letting $impeccable live start against the existing app shell and preserve the documented product/design identity during visual iteration.
+
+
+
+
Validation
+
+
Ran node .agents/skills/impeccable/scripts/context.mjs and confirmed existing PRODUCT.md and DESIGN.md.
+
Read reference/init.md, reference/product.md, and the live-mode setup guidance.
+
Ran node .agents/skills/impeccable/scripts/detect-csp.mjs; no CSP was detected.
+
Ran node .agents/skills/impeccable/scripts/live.mjs; it returned "ok": true, pageFiles: ["apps/web/app/layout.tsx"], and configDrift: null.
+
Confirmed and removed the temporary live script injection from apps/web/app/layout.tsx so production source is not coupled to a localhost helper port.
+
+
+
+
+
Issues, Limitations, and Mitigations
+
+
@pierre/diffs was unavailable as a runnable CLI, so the documentation includes a plain diff fallback.
+
The live helper was only boot-validated. No interactive browser live session was started because the request was initialization, not variant generation.
+
Future $impeccable live runs may temporarily reinject the localhost script marker while live mode is active; review that diff before committing unrelated UI work.
+
The generated server.json file is intentionally ignored to avoid committing local helper state.
+
+
+
+
+
Follow-up Work
+
+
Run $impeccable live during the next UI iteration to select elements in the browser and generate on-brand variants.
+
Run $impeccable critique apps/web/app/terminal.tsx if you want a scored review of the main terminal surface.
+
No Beads follow-up issue was created because this task completed the requested initialization.