bytes_retrieved (the "With context-mode" half of kept_out_pct) was 0 across
all 124,454 prod events — the dashboard's Context Savings card showed
"Retrieval signal pending" forever. Root cause: PostToolUse never fires for
the plugin's OWN MCP tools, so the hook-side extractMcpToolCall path never
observes ctx_search / ctx_fetch_and_index. Their response bytes are measured
only server-side (in tool_calls), which never reaches the forward stream.
Bridge the two processes through a tmp marker keyed by the session DB
basename (the one id both the server and the hook resolve reliably —
CLAUDE_SESSION_ID is not guaranteed in the server env). The server appends
each retrieval's response byte count; the next ordinary-tool PostToolUse
consumes the marker (consume-once) and emits a forwardable event carrying
bytes_retrieved, which session-loaders already stamps onto the platform
payload. Mirrors the existing redirect / latency marker handshake.
This is a server-emit fix, not a hook-extract fix: releasing the existing
hook-based code would NOT have populated the field, since the hook never
sees these calls.
RGR: new retrieval-marker module (append/consume round-trip, accumulate,
consume-once, phantom-event guard) — 4 tests. Forward stamping already
covered by platform-bridge-wire. tsc clean; 31 related tests green. Verified
end-to-end against the live session DB path: 100279+3009 → 103288 consumed.
Section 1's "% kept out of context" rounded with toFixed(0), so a genuine
99.888% live-window ratio (8.9 MB kept out, 10.2 KB retrieval) printed a
bare "100%". One decimal (toFixed(1)) surfaces the honest 99.9% while the
no-retrieval edge still reads an honest 100.0%.
Existing ratio assertions parsed the integer with /(\d+)%/, which captured
the wrong digit once a decimal appeared; widened to /(\d+(?:\.\d+)?)%/.
Tests: new e2e drives formatReport with the real dev-worktree numbers and
asserts "99.9% kept out of context" (never a bare 100%). 37/37 green.
ctx_stats "Where you are now" counted only the main session_id, so the bar
read ~770 KB even though the conversation had spawned sub-agent and
ctx_execute sub-process sessions that did megabytes of redirected work.
Those sessions share the same worktree DB (worktreeHash = sha256(cwd)) but
carry their own session_ids, so the single-session filter dropped them.
getConversationWindowStats() scopes by worktreeHash so the user's OTHER
parallel worktrees never bleed in (a different cwd-hash is a different DB
file the prefix filter excludes), then splits the worktree by where
retrieval actually landed: sub-agent ctx_search/ctx_fetch returns hit their
own disposable windows, not the live one, so they count as kept-out, while
only this session's retrieval is charged as "With context-mode".
Empirically on the dev worktree: Without 8.9 MB / With 10.2 KB / 100% kept
out, vs the old single-session 770 KB.
Tests: 2 new (worktree split + parallel-worktree exclusion). tsc clean.
Root cause of the platform's $3,532 / cache_read 4.7B poison events (docs/handoff/cumulative-cost-bug.md): a Task tool_response.usage is the sub-agent's usage SUMMED across its entire run (every internal turn re-reads the cache → billions of cache_read tokens). extractAgentUsage priced that cumulative figure as one turn, producing four-figure per-event costs that out-totalled 10,817 legitimate events. Fix: extractAgentUsage no longer derives cost_usd from Task usage; it tags the event usage_scope='task_cumulative' (forwarded so the platform buckets it as lifetime spend, never per-turn) and keeps the raw tokens. Real per-turn cost comes only from per-turn signals (extractTranscriptUsage + each adapter's own session). TDD: cumulative-billions fixture asserts no cost_usd is emitted; the 6 Task-path pricing tests are removed (pricing is covered by pricing.test.ts + the per-turn path). Platform clamp (cost-sanity.ts) stays as defense-in-depth.
Records the tool_response byte size of ctx_search / ctx_fetch_and_index calls as a per-event bytes_retrieved field (suffix-matched, no regex; sandbox compute excluded) and forwards it alongside bytes_avoided. The platform can now compute kept_out_pct = avoided/(avoided+retrieved) per org — the full with/without ratio, not just the kept-out half. 27 tests green, tsc clean.
Refines 6d667348. The earlier fold counted ALL tool_calls.bytes_returned into 'With context-mode', including ctx_execute/batch/file sandbox stdout — work-output the model would see regardless. That crushed the redirect-savings bar to a false ~43%. Now folds ONLY retrieval returns (ctx_search + ctx_fetch_and_index) = the bytes the model paid to ACCESS the kept-out content. Empirical on this session: avoided 2.5 MB vs retrieval 125 KB = 95% kept out (vs the absurd '1 B / 100%' bug and the wrong '43%'). TDD: 8.1b now seeds a work tool (excluded) + two retrieval tools (included).
ctx_stats Section 1 rendered 'With context-mode: 1 B / 100% kept out' on tool-heavy sessions. Root cause: getRealBytesStats summed only session_events.bytes_returned (snapshot-replay only, ~0), while real MCP returns (ctx_execute/ctx_search stdout, redirect stubs) land in tool_calls.bytes_returned via incrementToolCall. Now folds tool_calls.bytes_returned across all three tiers (session/project/lifetime), so the bar shows the real bytes that entered context. Verified empirically: a live session read 0 from session_events but 423 KB from tool_calls. Conversation tier has zero overlap; lifetime overlap is ~0.4% (legacy sandbox-execute events). TDD: new 8.1b test seeds tool_calls and asserts the fold; existing tiers unchanged.
Root cause of the platform's $3,532 / cache_read 4.7B poison events (docs/handoff/cumulative-cost-bug.md): a Task tool_response.usage is the sub-agent's usage SUMMED across its entire run (every internal turn re-reads the cache → billions of cache_read tokens). extractAgentUsage priced that cumulative figure as one turn, producing four-figure per-event costs that out-totalled 10,817 legitimate events. Fix: extractAgentUsage no longer derives cost_usd from Task usage; it tags the event usage_scope='task_cumulative' (forwarded so the platform buckets it as lifetime spend, never per-turn) and keeps the raw tokens. Real per-turn cost comes only from per-turn signals (extractTranscriptUsage + each adapter's own session). TDD: cumulative-billions fixture asserts no cost_usd is emitted; the 6 Task-path pricing tests are removed (pricing is covered by pricing.test.ts + the per-turn path). Platform clamp (cost-sanity.ts) stays as defense-in-depth.
Records the tool_response byte size of ctx_search / ctx_fetch_and_index calls as a per-event bytes_retrieved field (suffix-matched, no regex; sandbox compute excluded) and forwards it alongside bytes_avoided. The platform can now compute kept_out_pct = avoided/(avoided+retrieved) per org — the full with/without ratio, not just the kept-out half. 27 tests green, tsc clean.
Refines 6d667348. The earlier fold counted ALL tool_calls.bytes_returned into 'With context-mode', including ctx_execute/batch/file sandbox stdout — work-output the model would see regardless. That crushed the redirect-savings bar to a false ~43%. Now folds ONLY retrieval returns (ctx_search + ctx_fetch_and_index) = the bytes the model paid to ACCESS the kept-out content. Empirical on this session: avoided 2.5 MB vs retrieval 125 KB = 95% kept out (vs the absurd '1 B / 100%' bug and the wrong '43%'). TDD: 8.1b now seeds a work tool (excluded) + two retrieval tools (included).
ctx_stats Section 1 rendered 'With context-mode: 1 B / 100% kept out' on tool-heavy sessions. Root cause: getRealBytesStats summed only session_events.bytes_returned (snapshot-replay only, ~0), while real MCP returns (ctx_execute/ctx_search stdout, redirect stubs) land in tool_calls.bytes_returned via incrementToolCall. Now folds tool_calls.bytes_returned across all three tiers (session/project/lifetime), so the bar shows the real bytes that entered context. Verified empirically: a live session read 0 from session_events but 423 KB from tool_calls. Conversation tier has zero overlap; lifetime overlap is ~0.4% (legacy sandbox-execute events). TDD: new 8.1b test seeds tool_calls and asserts the fold; existing tiers unchanged.
qwenChatJsonlPath uses path.join (OS-native), so on windows-latest the real path uses backslashes; the test hard-coded a forward-slash literal and failed. Build the expected with join() so it matches on every platform. macOS/Linux unaffected.
session-loaders now stamps positive bytes_avoided (context-saving signal) onto the forwarded payload, sourced from the canonical per-event bytesList. The platform FinOps P&L (migration 0027 + pnl.ts) sums it into savings_usd = bytes/4 x price for the CTO net/ROI. Completes the profit half of the cost+savings ledger. Wire test asserts the forwarded body carries bytes_avoided.
Exports the shared usage-event builder and adds optional native_cost_usd (preferred over catalog when a finite number) so per-adapter capture (openclaw/pi/omp/opencode ship native USD cost) reuses one structured-event + pricing path.
Wires extractTranscriptUsage into the Stop hook without double-counting: a per-session usage_cursor (session_meta) high-water mark means each Stop emits only NEW assistant turns since the last, via extractTranscriptUsageSince. Events run through attributeAndInsertEvents so they insert locally AND forward to the platform. Compaction-safe: a cursor that fell off the front emits only the last turn (bounded), never re-emits history. 122 tests green, tsc clean.
Adds extractTranscriptUsage + shared buildAgentUsageEvent: parses the session transcript JSONL char-algorithmically (no regex), sums per-turn assistant message.usage per model into structured agent_usage events, excludes isSidechain Task-subagent turns to avoid double-counting the separate subagent capture. This closes the dominant-spend gap the Task-only path missed. 53 tests green, tsc clean. Stop-hook live wiring (per-turn high-water mark) follows.
Consolidates the 5 vendor price files into one src/session/model-prices.json (61 models), moves the module to src/session/pricing.ts beside its only consumer (extract.ts), and drops the dev-only litellm NOTES from tracking (litellm base stays gitignored). Wave 2b: extractAgentUsage emits model_id + input/output/cache_read/cache_creation tokens + cost_usd as structured top-level event fields, replacing the colon-string, so the platform persists them as columns. 45 tests green, tsc clean.
Replaces the hardcoded 5-model Claude price table (extract.ts) with a curated, websearch-sourced catalog of 63 models across Anthropic/OpenAI/Google/Chinese/other vendors, behind a small lookup/compute module (src/pricing/catalog.ts). Non-Claude models now price off their own rows instead of defaulting to Claude Sonnet. Adds provider/ prefix normalization, native-cost passthrough, and null+warn on unknown models. LiteLLM ~2900-model catalog kept as a dev-only refresh base (tools/pricing/, gitignored), not bundled.
All pages already fetch stats.json (CI-maintained install count) at runtime. Static fallbacks and meta descriptions now match the live value (331,200+) instead of a hardcoded figure, so crawler-visible meta no longer contradicts the dynamically rendered count.
Apply Emil Kowalski design-engineering review to index, insight, and
context-saving landing pages:
- press feedback (:active scale .97/.99) on buttons + link-cards
- explicit transitions instead of `all` shorthand, on a shared
--ease-out / --ease-in-out custom ease-out token
- :focus-visible rings for keyboard affordance
- origin-aware dropdown (grows from trigger) + caret rotate on open
- touch-safe hover (no sticky lift on tap via @media hover:none)
- prefers-reduced-motion: kill movement, keep content visible
(parity across all three pages; only context-saving had it before)
On Pi, context-mode loads in-process and the MCP bridge wrote diagnostics to
process.stderr — which IS Pi's raw-mode TUI terminal. The #854 idle-reaper's
"self-shutdown" line rendered into the editor box and blocked typing (reported
by @carolitascl, confirmed by @frsswq). Two coupled defects, both fixed:
1. Diagnostics on the wrong channel. All pi-adapter stderr writes (the
forwarded MCP-child stderr + the bridge's own warnings) now route to
pi.logger (Pi's rotating ~/.omp/logs), never process.stderr — the documented
channel for in-process extensions. #472's crash-diagnostic intent is
preserved (captured to the log, not swallowed). Helpers makeBridgeDiag +
splitDiagLines (regex-free; replaces the old split(/\r?\n/)).
2. The foreground session shouldn't drop at all. The #854 reaper reaped the
ACTIVE interactive session's child after 180s idle — a human pause must not
drop ctx_* tools. The foreground child is now spawned with the reaper
disabled (CONTEXT_MODE_BRIDGE_IDLE_MS=0), decided from ctx.hasUI on
before_agent_start; subagents (hasUI:false) keep the reaper so abandoned
children still can't accumulate (#854). The foreground child is still reaped
on real parent death by the ppid/signal watchdog. Deciding on the first
before_agent_start is provably safe (Pi wires uiContext before the first
prompt) — documented as an invariant, no runtime latch needed.
Also DX-tuned the reaper notice (idleReapMessage): human units, reassures
auto-reconnect, drops the "self-shutdown" jargon.
Scope verified across all 17 adapters: only pi forwards child stderr into a
raw-mode TUI; omp (separate MCP subprocess), opencode/openclaw (embedded mode +
host logger), and the 13 stdio-subprocess hosts isolate MCP stderr — none
affected, none need a fix.
TDD RGR; two Pi-Architect design reviews + one adversarial implementation
review; no-regex; typecheck + pi/lifecycle suites green (169 tests).
The background install of the three optional fetch deps (turndown,
turndown-plugin-gfm, @mixmark-io/domino) spawned npm with `shell: IS_WIN32`.
On Windows + Node, `shell: true` DROPS the `cwd` option, so the spawned cmd.exe
runs in an arbitrary working dir (C:\Windows under Claude Code). `npm install`
then tries to create `C:\Windows\node_modules` -> EPERM on every MCP boot, and
a cmd.exe window flashes each time; the install never persists, so the
existsSync guard never short-circuits and it re-fires every session (reported
by @lravizzoni with npm-debug-log evidence).
Prefer running npm's own CLI through node directly (no `.cmd` shim, no shell):
resolve `npm-cli.js` beside `process.execPath` and spawn it with
`shell: false` (honors `cwd`) + `windowsHide: true` (no console window). Fall
back to the `npm.cmd` shim only when npm-cli.js can't be located, so a working
host — e.g. a POSIX layout where npm-cli.js isn't beside node — never regresses
(POSIX already used shell:false, so its behavior is unchanged). Also surface
spawn failures and non-zero exits to stderr; this EPERM was invisible for
months behind stdio:"ignore" + an empty error handler.
Tests: structural regression pins the node/shell:false/windowsHide/fallback
contract (regex-free, matches the start.mjs test pattern); a portable behavioral
test pins the runtime property the fix relies on (shell:false honors cwd). #634
background-install contract preserved. Needs Windows CI / on-device confirmation.
On Linux, start.mjs re-execs under Bun to dodge the better-sqlite3 SIGSEGV
(#564); the node proxy forwards stdin to the Bun child. Its stdin EOF handler
was a no-op and the proxy parked forever, so when the MCP client exited the
proxy never closed the child's stdin nor exited — the child's direct parent
stayed alive (defeating its ppid watchdog) and its stdin never EOF'd, leaving
an orphaned pair pinning a CPU core at PPID=1 (reported by @elhoim: 5 orphans,
~3 of 6 cores).
The proxy now tears the child down on stdin end/close/error: forward EOF for a
graceful self-reap via the child's own lifecycle guard, then escalate SIGTERM
(2s) -> SIGKILL (5s) so a wedged child can never outlive its client. Re-exec
under Bun is unchanged (#564 preserved). The escalation timers are deliberately
not unref'd so teardown liveness never depends on the top-level await surviving
a refactor.
Behavioral e2e (real proxy bytes, old-vs-new x graceful-vs-wedged): old leaks
the child; new reaps it in ~4ms graceful / 5s wedged. Source-introspection test
pins the contract; lifecycle + start.mjs suites green.
ctx_execute_file fed its `path` straight into resolve(projectRoot, path),
so an absolute path (/home/user/secret, /etc/passwd) or a `../` traversal
escaped the workspace and read any file on the host. With Claude Code's
sandbox enabled the host denied such a read, but the agent retried through
the MCP sandbox — and the host's MCP approval prompt cannot inspect the
tool's input params, so the escape was invisible to the approver.
Mitigation (defense-in-depth):
- New pure, no-regex primitive isPathInsideProject() + policy wrapper
evaluateProjectContainment() in security.ts: a path resolving outside the
project root (absolute escape, `..` traversal, or symlink-canonical
escape) is refused. The symlink check mirrors evaluateFilePath().
- ctx_execute_file handler runs checkProjectBoundary() before execution.
- Escape hatch reuses the host's existing permissions.allow Read(...) rules
(via generalized readToolPermissionPatterns), NOT a bespoke context-mode
env that would rot into dead code — an out-of-project grant lives once in
the same config Claude Code itself honors.
- MCP approval titles for ctx_execute / ctx_execute_file now read as code
execution, since refs show the host prompt renders only the tool title +
raw args (the title is the one server-controlled signal).
Residual: ctx_execute / ctx_batch_execute run arbitrary code and still
inherit the process FS; the boundary guard hardens the file-read tool, not
a full OS sandbox. Host-level sandboxing remains the primary control.
Tests: tests/security/project-boundary-852.test.ts (containment geometry,
symlink escape, allow-rule opt-in, server wiring). typecheck green; full
suite 4484 passed. Bundles intentionally not committed (CI rebuilds on main).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Defense-in-depth fix for the pi/omp do-nothing loop where a casual past
phrase ("that's fine for now") froze as a priority-3 role and got
re-injected as a standing <behavioral_directive> every turn.
1. Classifier (extract.ts): require a persona/standing-directive cue
(algorithmic prefix membership, no regex) so conversational
acknowledgements no longer classify as role. Legitimate multilingual
role prompts (issue #535 corpus) and directives ("always respond in
TypeScript", "act as a security reviewer") still classify.
2. Pi injection (adapters/pi/extension.ts): filter role out of the
per-turn before_agent_start active_memory injection. Roles stay in the
DB and queryable via ctx_search; intent/skills/decisions/resume
snapshot are unaffected. Removes the dead behavioralDirective extract.
3. Framing (hooks/routing-block.mjs): soften session_continuity so a
captured snapshot is a memory aid, not an inescapable 'active until
revoked' standing order; the user's latest message takes precedence.
RED->GREEN tests per slice; no new regex; typecheck green.
resolveHookRuntime baked process.execPath (a version-pinned path under
mise/asdf/nvm, e.g. ~/.local/share/mise/installs/node/20.1.0/bin/node)
into every hook command with no liveness check. A routine Node patch
upgrade deletes the old version dir, the cached path dangles, and every
hook spawn fails with ENOENT — silently killing context-mode for that
user.
Extends the #800/#803 liveness-guard pattern (resolveJavascriptRuntime,
Homebrew Cellar ENOENT) to the hook runtime path via liveNodeRuntime():
use the pinned execPath IFF it exists on disk (preserving the #190
snap-node / #369 Windows MSYS reasons it was pinned), else re-resolve a
working node from PATH. Covers mise + asdf + nvm path shapes.
#782: On Windows Git Bash, a bare `mvn` runs Maven's POSIX script which on
the mingw branch leaves CLASSWORLDS_JAR as a POSIX path that native java.exe
cannot resolve (ClassNotFoundException). Rewrite the bare `mvn` token to
`mvn.cmd` (native Windows launcher) in shell scripts. This avoids re-enabling
global MSYS path-conversion suppression that #826/#791 removed for native git.
#788(a): Non-shell runtimes (python/js/ts/...) ran with cwd=sandbox tmpDir, so
repo-relative checks failed depending on language. Unify cwd to the project
root (or per-call override) for all languages; the script file still lives in
the sandbox and is passed by absolute path.
#788(b): .ctx-mode-* temp dirs leaked on Windows when SQLite -wal/-shm handles
lingered and a single rmSync threw EBUSY/EPERM into a silent catch. Add
cleanupTmpDir() with rmSync maxRetries/retryDelay backoff.
Keep context-mode.com/insight as the single source of truth for the ctx_insight tool/CLI/skill/README copy — remove the platform.context-mode.com sign-in line and the $20/seat pricing, which can change. Also delete stale root docs: release-notes-v1.0.148.md, llms-full.txt, llms.txt.
Insight is now a hosted B2B product at context-mode.com/insight (sign-in + org analytics at platform.context-mode.com). The local build-and-serve dashboard is removed; ctx_insight (MCP tool + CLI) now opens the hosted URL in the default browser via the shared openBrowserSync helper.
- server.ts: replace the ~250-line build/spawn/port handler with a browser-open handler; drop _insightChild tracking, ChildProcess/execSync imports, and the shutdown kill hook. Keep openBrowserSync/killProcessOnPort helpers and the ctx_upgrade legacy insight-cache cleanup (#469).
- cli.ts: 'context-mode insight' opens the hosted URL.
- openclaw: update ctx_insight stub description.
- Remove the insight/ dashboard app and its package.json files entries.
- Tests: drop the port-schema, containment, and CORS suites for the removed local server; keep helper, #469, and #697 description tests.
- Docs: README + ctx-insight SKILL point at the hosted dashboard.
The hero green cursor was driving compositor work harder than it
needed to: steps(2) + a 24px box-shadow meant the GPU repainted
the full glow on every tick, which user reported as a low-FPS feel.
- animation timing: 1.06s steps(2) → 1.4s ease-in-out (smooth pulse
between opacity 1 and 0.28, terminal-feeling without hard flicker)
- box-shadow blur: 24px → 18px and alpha .45 → .40 (lighter recompose)
- add will-change:opacity + translateZ(0) so the cursor lives on its
own composited layer (no repaint of surrounding text)
- topnav cursor: same easing pass, renamed keyframe to nav-blink so
the hero and nav don't share a single declaration name
- prefers-reduced-motion now resolves to a static 0.85 opacity cursor
instead of a stuck-on solid block
OG PNGs are static, so the 287,000+ number drifted away from the
landing page (which already reads stats.json live). Solution:
- Mark every user-count occurrence in master-og.html and
context-saving-og.html with class js-user-count
- render-og.mjs now fetches the same stats.json before opening the
browser, then runs a one-shot DOM swap on every .js-user-count
element before the screenshot
Result: each render bakes the freshest count in, no manual update
needed. Today's render shows 290,000+ across all three banners.
Two regressions snuck back in when the /context-saving rename agent
copied from the next branch (which never had the hero pattern fix).
Re-applying on main:
- .cm-master + .cm-wordmark: inline-flex → flex (so the hero-tag
pill never shares a baseline-aligned row with the wordmark)
- .hero-tag: margin-bottom → margin-top + align-self:flex-start
(sits below the wordmark on its own line)
- Insight wordmark italic: 1.18em → 1em, margin-left .12em → .14em
- HTML order: <h1> wordmark first, <p class="hero-tag"> after
(matches master/insight/context-saving)
Plus making the Insight landing's "287,000+ developers" references
dynamic (.js-user-count + the GitHub stats.json fetch shim), so the
number matches the OSS plugin's source-of-truth count instead of
drifting like the screenshots showed (290k+ on /, 287k+ here).
Same typography pass on the dark surface banner:
- font-weight 600 → 700
- font-size 120px → 104px
- letter-spacing -.04em → -.045em
- cursor 34×100 → 31×96 (.30em × .92em proportional)
All three OG banners now share one wordmark system.
User noticed the two preview banners didn't share weight/size. Aligning:
- Both wordmarks: font-weight 700, font-size 104px, letter-spacing -.045em
(Master was 130/700, Insight 104/600 — visible "Master heavier + bigger"
inconsistency)
- Insight italic ".italic": 1.18em → 1em with letter-spacing -.025em
(matches the landing fix that unified visual baseline between sans and
italic halves of the wordmark)
- Cursor block: unified to .30em × .92em proportional sizing
(Master 38×108 / Insight 30×88 → both 31×96)
- Cursor translateY trimmed 8→6 to compensate for smaller font
Both banners now read as the same brand mark, just with a longer
right-hand extension on Insight.
Marketing rename. The OSS plugin's actual product name (context-mode)
stays the same; only the marketing section URL / nav label / OG asset
name / favicon name change.
- /oss → /context-saving route, 301 redirect for backwards compat
- Nav label "OSS Plugin" → "Context Saving" on master + insight + context-saving
- Rename: oss.html → context-saving.html, favicon-oss* →
favicon-context-saving*, apple-touch-icon-oss → apple-touch-icon-context-saving,
og/oss* → og/context-saving*
- Re-render OG banner with /context-saving URL stamp
Same wordmark-above-tag pattern that landed on master + insight:
- Switch .h1 from inline-flex to flex so the wordmark always claims
its own block row
- Reorder HTML: $context-mode wordmark first, hero-tag pill below
- Move tag margin from bottom to top and add align-self:flex-start
- Reorder hero so wordmark renders above the tag pill on both
landings (matches the original master mock the user signed off on)
- Drop Insight italic ".italic" from 1.18em to 1em so the "Insight"
serif portion shares a baseline height with the sans "Context Mode"
half — wordmark visual weight now matches Master at 88px
- Move .hero-tag margin from bottom to top and add align-self:flex-start
to honor the new HTML order under display:flex
The hero-tag pill was rendering on the same baseline-aligned row as
the wordmark when the wordmark string fit on one line (Master:
"Context Mode" fits, so tag and wordmark sat side-by-side; Insight:
"Context Mode Insight" wraps, so they stacked). Both were
display:inline-flex.
Promoting .cm-master and .cm-wordmark to display:flex forces the
wordmark onto its own block line, so tag → wordmark → sub always
stacks the same way across both landings.
Lock in the Context Mode brand hierarchy on context-mode.com:
- / serves new master landing (family-level)
- /oss redirects to the OSS plugin terminal landing
- /insight serves the first Solution on Context Mode Platform
Cross-cutting changes:
- Cursor brand mark (▌) unified across all 3 wordmarks, color-adapts per surface
- shadcn card primitives locked in as the only card pattern (no exceptions)
- Sticky top nav with Solutions dropdown (Insight live, Memory/Audit/Cost coming)
- Hero wordmark size aligned across all 3 landings — clamp(3rem,8vw,5.5rem)
Per-landing detail:
- Master (index.html): master family hero, OSS vs Platform split, Solutions menu
- OSS (oss.html): dark terminal aesthetic, animated cursor blink, /insight teaser
- Insight (insight.html): light editorial, $20/seat anchor, 6 persona MCP demos
rewritten ChatGPT-style (conversational answers, source attribution)
Brand assets shipped with the landings:
- SVG + PNG favicons (master/insight blue cursor, oss green cursor)
- 1200x630 OG preview banners for X / LinkedIn / Slack / iMessage
- Render script (web/og/render-og.mjs) for regenerating OG banners
Worker routes /oss and /insight to the new files
Lock in the Context Mode brand hierarchy on context-mode.com:
- / serves new master landing (family-level)
- /oss redirects to the OSS plugin terminal landing
- /insight serves the first Solution on Context Mode Platform
Cross-cutting changes:
- Cursor brand mark (▌) unified across all 3 wordmarks, color-adapts per surface
- shadcn card primitives locked in as the only card pattern (no exceptions)
- Sticky top nav with Solutions dropdown (Insight live, Memory/Audit/Cost coming)
- Hero wordmark size aligned across all 3 landings — clamp(3rem,8vw,5.5rem)
Per-landing detail:
- Master (index.html): master family hero, OSS vs Platform split, Solutions menu
- OSS (oss.html): dark terminal aesthetic, animated cursor blink, /insight teaser
- Insight (insight.html): light editorial, $20/seat anchor, 6 persona MCP demos
rewritten ChatGPT-style (conversational answers, source attribution)
Brand assets shipped with the landings:
- SVG + PNG favicons (master/insight blue cursor, oss green cursor)
- 1200x630 OG preview banners for X / LinkedIn / Slack / iMessage
- Render script (web/og/render-og.mjs) for regenerating OG banners
Worker routes /oss and /insight to the new files
Per Mert directive 2026-06-07. Enterprise tier surface is being
pulled until the product story is clearer. Strips:
- src/routes/enterprise.tsx (full page: persona grid EM/CISO/DevOps,
2x2 value props, design-partner mailto CTA)
- Enterprise nav entry + Building2 icon import from src/routes/__root.tsx
- Auto-regenerated src/routeTree.gen.ts without /enterprise route
Per Mert directive 2026-06-07:
- Brand normalized to "Context Mode Insight" (singular, capital I).
"Insights" plural → "Insight". 34 substitutions.
- "Analytics" / "analytics" eliminated everywhere — 10 user-facing
occurrences across index.html + oss.html, all → "Insight" /
"insight".
- twitter:creator @AcikAnaliz → @mksglu.
URL surface stays minimal: only / and /oss. Dropped the legacy
/insights alias from worker.js — no /insight or /insights route
exists. Canonical and og:url back to https://context-mode.com/.
Added baseline SEO files:
- robots.txt (Allow all + sitemap pointer)
- sitemap.xml (two URLs — / and /oss)
- og:image meta points to /og-insight.png (asset still TODO,
declared so the OG harvester knows what to expect)
Title trimmed for SERP fit:
"Context Mode Insight — AI engineering signal at $20/seat" (56 ch)
Owner directive (2026-06-07): "layout u bozuyor biraz ortali oldugu icin,
pricing'de yaptigin gibi yap" — the FAQ chapter was the only chapter using
wrap-narrow (840px tight-center) while every other chapter uses wrap
(1280px) with an inline-centered intro block. The inconsistency broke
page rhythm.
Mirror the Pricing chapter pattern shipped in 48cfe06 (web/index.html
L461-468):
- section uses regular .wrap container (1280px) instead of .wrap-narrow
- intro block .rev: max-width:880px; margin:0 auto; text-align:center
- .rule: margin-left:auto; margin-right:auto
- .faq details list: max-width:840px; margin auto (preserves the
comfortable reading width of wrap-narrow inside a centered column
while the questions/answers stay left-aligned by default)
Also adds id="faq" to the chapter for anchor parity with id="pricing".
Files:
- web/index.html L1292-1301 (FAQ intro block — was using wrap-narrow
+ no centering styles → now matches pricing's centered-axis pattern)
- mirrors web/index.html L461-468 (pricing intro block — the
canonical centered-axis treatment)
No content change. Visual treatment only — every <details> question +
answer body is byte-identical.
Mert directive: "What this is not" deny-list section ("No SSO. No SOC 2
report. No on-prem option ...") removed entirely. The pricing chapter
also dropped the competitor-peer-tier callout ("Same band as Cursor
Pro, GitHub Copilot Business, Vercel Pro — the AI-dev-tool tier you
already budget for") which Mert flagged as unnecessary mention.
The remaining pricing chapter elements are now centered (kicker, H2,
rule, lead, card) so the chapter reads as one focused vertical block
instead of the prior left-headline + center-card asymmetry that broke
the page rhythm.
Also softened the SSO/SOC 2 FAQ answer:
before: "If your buying process requires SSO or a SOC 2 report, run
the OSS collector self-hosted (free, ELv2 source-available);
we will revisit Enterprise after the first Series A."
after: "The OSS collector is source-available under ELv2 and can be
run self-hosted if your buying process requires those
controls."
The Series A reference removed (out of band for buyer-facing copy);
competitor mentions in the FAQ removed.
Net delta: 5 line removal in pricing chapter + 1 line FAQ rewrite.
The Linear / Notion / Jira mentions in the §Integrations chapter stay
— those are functional integration partners, not pricing peers.
Wave PS (platform repo, commit d511924) deleted the /signup route
entirely. Card capture moved to Stripe-hosted Checkout via the OAuth
callback → /?signup=needs_checkout&… → POST /auth/signup-with-checkout
flow. Per Mert directive: "Bizim Platform'da /signup isminde bir
endpoint imiz bir Page'imiz yok ... Signup olmamalı."
Every CTA on this landing pointed at platform.context-mode.com/signup
which now returns 404 (route removed in routeTree.gen.ts). This commit
retargets all 11 references to the platform root:
- L39 JSON-LD offers.url
- L441 §01 chapter CTA
- L549 §02 chapter CTA
- L1166 §08 chapter CTA
- L1275 §09 chapter CTA
- L1308 pricing card CTA
- L1327 lead paragraph link
- L1396 FAQ answer link
- L1441 §10 chapter CTA
- L1459 footer micro-copy
- L1479 sticky bottom CTA
The platform root (https://platform.context-mode.com) renders the
WelcomeLanding component for unauthenticated visitors (Google + GitHub
OAuth buttons). After OAuth, the new-user path lands on Home with the
?signup=needs_checkout marker which kicks off Stripe Checkout.
No copy change beyond the URL — every "Start for $20 / seat" button
still says the same thing, just points one URL segment shorter.
Owner directive 2026-06-06: pricing needs prioritized work — make
it more prominent. $20/seat is the value claim; burying it nine
chapters deep was wrong for a self-serve funnel.
Option A (per the directive): relocate the entire <section
id="pricing"> block to immediately after the trust strip, before
the PROBLEM chapter (§01). Visitors now see the price band within
the first scroll after they see the hero + trust signal.
- Cut <section id="pricing"> block (36 lines incl card, deny-list)
from between SECURITY (§10) and FAQ
- Paste it directly after the trust strip's closing </section>,
before the PROBLEM (§01) chapter
- Drop the "§ 11 · pricing" section-num prefix — pricing is no
longer a numbered chapter in the storytelling sequence; it is
a top-funnel anchor right after the trust signal. The remaining
§01-§10 numbered chapters keep their ordering. FAQ's existing
"§ 12 · faq" label is preserved (pre-existing pattern, not
touched by this wave)
- pricing-card, pricing-cta, pricing-fine, pricing-deny-list CSS
classes are untouched and continue to render the card identically
- The #pricing anchor still resolves; in-page links to #pricing
(none currently exist in this file) would still jump correctly
Storytelling rhythm post-move:
Hero -> Trust strip -> Pricing (NEW slot) -> Problem (§01) ->
Solution (§02) -> ... -> Security (§10) -> FAQ -> Footer
Verify: section count unchanged (13 open / 13 close); id="pricing"
appears once; pricing block precedes the §01 problem chapter.
Owner reported (2026-06-06) the trust strip rendered with 20
company names stacked in a tall right-side column instead of a
horizontal wrap-row.
Root cause: .trust-grid used display:grid with grid-template-columns:
auto 1fr. The first column (auto) sized to its content — the
trust-headline block contains a long sentence + <br> + lead sentence,
so 'auto' computed to ~75% of viewport width. The second column
(1fr) got only the leftover narrow strip, forcing 20 brand spans
to wrap one-per-line vertically. The 900px media-query breakpoint
hid the bug on small screens but not desktop.
Fix (surgical, no redesign):
- .trust-grid: display:grid;grid-template-columns:auto 1fr;gap:48px
-> display:flex;flex-direction:column;gap:20px
- .trust-headline: add max-width:880px so headline doesn't span
the full container on ultrawide
- .trust-brands: add align-items:center for vertical rhythm with
the row above (flex-wrap:wrap was already correct)
- Replace 900px breakpoint with a 600px breakpoint that just
tightens brand gap/font — desktop is now correct, mobile too
Result: headline renders on top (max-width 880px), brands wrap
underneath as a horizontal row that flows naturally across 1-5
visual rows depending on viewport width.
Append canonical OSS/Platform split paragraph at the end of the
"Insights - the analytics layer" chapter body, before the gtm-strip
CTA per PRD §4.3:
"The OSS plugin stays free forever - it is the collector. The
Platform is where your team sees what's happening. Pro tier is a
flat \$20 per seat per month. No tiers, no trial, no surprise
metering."
This is the canonical sentence Mert can cite in any future copy
review. New .chapter-close CSS hook (--bg-alt panel, --accent
left border, --accent-dark emphasis) so the line reads as a closing
emphasis without breaking the section's existing rhythm.
Replace the lingering "Join the waitlist" gtm-strip CTA at the end
of this chapter with "Start for \$20 / seat" so the visual primary
path stays consistent with hero / pricing / FAQ.
L1437 before:
"Private beta . onboarding 5 design-partner orgs . free Team-tier
access during the design phase . shape the patterns we ship next."
After:
"Pro \$20 / seat / month . platform.context-mode.com . OSS
collector free forever."
Per PRD §4.11. Final forbidden-string elimination at the footer
level. Adds an underlined link wrapper on platform.context-mode.com
so the line itself converts.
The page is long (1.5K+ lines, multi-screen scroll). Persistent
bottom-of-viewport CTA improves conversion without breaking chapter
rhythm per PRD §4.10.
HTML: fixed-position bar appended before </body> with:
- "Pro \$20 / seat / month. No tiers, no trial, no surprise bills."
- "Start ->" button to platform.context-mode.com/signup
CSS in existing <style> block using available vars (--bg-alt for
panel, --border for top-edge, --text/--text-2 typography,
--accent-dark hover). No --bg-elevated -> --bg-alt instead.
translateY(100%) initial state, .visible toggles to translateY(0)
with 350ms ease transition. 640px breakpoint stacks vertical.
JS: passive scroll listener flips .visible after scrollY exceeds
80vh. aria-hidden flips in sync so screen readers ignore the bar
until it materialises.
Remove 3 stale FAQ pairs (each containing "private beta", "design
partner", "free Team-tier", or "join the waitlist for design-partner
inquiries"):
- "Is Insights live yet? How do I get in?"
- "On-prem option?" (design-partner inquiries)
- "What other Solutions are on the roadmap?" (design partners
welcome) - content folded into "How does Insights relate to
Context Mode Platform?" so Audit/Risk Score is still surfaced
Add 4 new pricing-aligned FAQ pairs at the top per PRD §4.9:
- How much does Insights cost?
- Can I try it free?
- What is your refund policy?
- Do you offer SSO / SAML / SOC 2?
Also fix the "How are the patterns built?" answer which had stale
counts:
- 219 patterns -> 222 patterns (matches catalog)
- 8 categories -> 9 categories (cost category included)
- 10 adapters (Kimi listed) -> 14 adapters (rebuilt list matches
Slice 4 adapter chapter)
- 15 platform integrations on OSS side -> 16 (configs/ directory
count)
FAQ closing gtm-strip CTA: "Still have questions? Join the waitlist"
-> "Start for \$20 / seat" pointing at signup. Hand-onboarding orgs
still reach the waitlist via the demoted Slice 7 form upstream.
Per PRD §4.7 - waitlist stays for orgs that want guided rollout, but
the language drops every "private beta" and "free Team-tier" claim:
- H2: "Insights is in private beta." -> "Want a guided rollout? Tell
us about your team."
- Lead: rewritten to point self-serve customers to
platform.context-mode.com/signup and frame this form as
hand-onboarding only
- monthly_budget radio group removed entirely (price is fixed
\$20/seat x N - no budget band to guess) - replaced with
seat_count number input (min 1, max 500, placeholder "e.g. 47")
- "No card required... 5 design-partner orgs" -> "We will reply
within 24 hours"
- "Early access" bullet -> "Guided rollout" bullet
- "Free Team-tier during design phase" bullet -> "Same Pro tier,
same price" bullet (\$20/seat from day one, cancel via Stripe)
- Section-num "11" -> "12" (pricing took the prior 11 slot)
Also fix trust strip L449 stale "currently in private beta" copy to
match the new self-serve framing - this site is not called out in
PRD audit table but it carried the forbidden "private beta" string.
Form backend unchanged - still POSTs to the same web3forms endpoint.
New section #pricing per PRD §4.8:
- "One tier. Twenty dollars per seat. Predictable." H2
- Pricing card: $20/seat/mo, 8 feature bullets (222 patterns, 14
adapters, 90-day retention, persona views, etc.), CTA to
platform.context-mode.com/signup, fine print on no annual/volume/
trial/free
- Deny list: "What this is not" - no SSO, no SOC 2, no on-prem, no
dedicated support. Revisit Enterprise after Series A.
CSS inline in existing <style> block using established vars:
--border, --border-light, --bg-alt (no --bg-elevated exists), --bg-warm
for deny list, --text/--text-2/--text-3 typography, --mono/--serif
fonts, --accent-dark hover. Mobile breakpoint at 640px shrinks card
padding + price font.
Replace prior "Join the waitlist" gtm-strip CTA on Roles section with
"Start for \$20 / seat" so Roles -> Pricing flows visually toward the
new primary path.
Ground abstract pricing in concrete persona numbers:
- Sarah (CTO) ROI card: "Org pays 47 x \$20 = \$940 / month. Renewal
is a Stripe quantity update, not a procurement event."
- Sophia (FinOps) cost card: "The \$50K/mo AI line item now has a
sibling Platform line: \$940/mo for visibility into the other \$50K."
47-engineer reference org math (47 x \$20 = \$940) becomes the anchor
the rest of the page can cite. New .persona-price-callout CSS using
--accent / --accent-light / --accent-dark vars.
Per PRD §4.4. Bill/Ekrem in PRD map to existing Sarah/Sophia personas.
Add 5 cards (alphabetical within paradigm groups):
json-stdio (8): Claude Code, Codex, Cursor, Gemini CLI,
JetBrains Copilot, Kiro, Qwen Code, VS Code Copilot
ts-plugin (3): Kilo, OpenClaw, OpenCode
mcp-only (3): Antigravity, Pi, Zed
Remove Kimi card (not in canonical 14 per PRD §4.6 list). Sources
verified against src/adapters/ + configs/ inventory. Adapter-paradigm
.mcp CSS class already exists at L303 - no new style needed.
Update hero-sub "10 more AI assistants" -> "9 more" and hero-stat
"10" -> "14" to match. Update chapter lead to "14 AI assistants via
three paradigms". Replace waitlist CTA in gtm-strip with $20 signup
CTA so the chapter's call-to-action matches the new hero primary.
Per PRD §4.6.
- Insert hero-price-anchor under hero-sub: "OSS collects. Platform understands. $20 / seat / month."
- Primary CTA: "Join the waitlist" -> "Start for $20 / seat" -> platform.context-mode.com/signup
- Secondary CTA: "See the personas" -> "Or join the waitlist for hand-onboarding"
- New .hero-price-anchor CSS using existing --accent-light / --accent-dark / --mono vars
Per PRD §4.2. Waitlist demoted to secondary path for hand-onboarding orgs.