diff --git a/components/graph-context-control.tsx b/components/graph-context-control.tsx
index 2f24a39..fa69113 100644
--- a/components/graph-context-control.tsx
+++ b/components/graph-context-control.tsx
@@ -11,15 +11,19 @@ import { formatTokens } from "@/components/usage-format";
/**
* The knowledge-context budget control (vision-context-injection.md) — the
* "session data" section's one input, shown where sessions START: the home
- * (graph) view's new-session dock and the /chat/new draft. Input before
- * output: the panel that will later show what the session contributed to
- * the graph asks first what the graph will contribute to the session.
+ * (graph) view's new-session dock and the /chat/new draft. It renders in
+ * the StatsBar's LEFT slot — the very place session data (context/cost)
+ * lives once a session exists, which pre-chat is free real estate (just
+ * "—"). Input before output: the panel that will later show what the
+ * session contributed to the graph asks first what the graph will
+ * contribute to the session.
*
- * One quiet slider row: the token budget for the prior-knowledge block the
- * injection steers in at session start. 0 = off; absent settings show the
- * DEFAULT (1024 — on). The value rides ChatSettings (the same lifted
- * settings the model picker uses), so it persists pre-chat via the same
- * deferred PUT. Sessions never show this control — the injection is
+ * One quiet compact row sized to sit in the stats band beside the model +
+ * thinking pills: short slider, "tok" readout (abbreviated — "4.1k tok"
+ * stays on one line where "4.1k tokens" wrapped). 0 = off; absent settings
+ * show the DEFAULT (1024 — on). The value rides ChatSettings (the same
+ * lifted settings the model picker uses), so it persists pre-chat via the
+ * same deferred PUT. Sessions never show this control — the injection is
* one-shot at session start, so mid-session there is nothing to decide.
*/
export function GraphContextControl({
@@ -32,12 +36,9 @@ export function GraphContextControl({
testId?: string;
}) {
return (
-
);
diff --git a/components/home.tsx b/components/home.tsx
index 082dac5..5e5885b 100644
--- a/components/home.tsx
+++ b/components/home.tsx
@@ -72,17 +72,6 @@ export function Home() {
px-4) so nothing shifts when a session starts (owner). */}
- {/* The session-data strip (vision-context-injection.md): what the
- graph will contribute to the session, decided before the session
- starts. Rides the same lifted settings as the model picker. */}
-
{/* Same as the chat view's under-composer stats bar (owner) — no
- session yet, so money/context fold as "—"; the model +
- thinking control still works (deferred save). */}
+ session yet, so the free LEFT side carries the knowledge-context
+ budget (session-data slot), while the RIGHT keeps the model +
+ thinking control (deferred save). */}
+ setSettings((s) => ({ ...s, graphContextTokens: tokens }))
+ }
+ />
+ }
/>
diff --git a/components/new-session.tsx b/components/new-session.tsx
index 496b505..f309922 100644
--- a/components/new-session.tsx
+++ b/components/new-session.tsx
@@ -99,16 +99,6 @@ export function DraftShell({
- {/* The session-data strip (vision-context-injection.md) — same
- control as the home view, riding the same lifted settings. */}
-
diff --git a/components/stats-bar.tsx b/components/stats-bar.tsx
index 749d2f9..2520325 100644
--- a/components/stats-bar.tsx
+++ b/components/stats-bar.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useRef, useState, type ReactNode } from "react";
import { contextWindow, sessionCost, turnCost, type ChatSettings, type TurnUsage } from "@/src/core";
import { formatTokens, formatUsd, prettyModel } from "@/components/usage-format";
import { ModelControl } from "@/components/model-picker";
@@ -11,6 +11,12 @@ import { ModelControl } from "@/components/model-picker";
* LEFT-aligned: context window + session cost; RIGHT-aligned: model +
* thinking level. Clicking the left side opens the per-turn detail popover.
*
+ * Pre-chat views (home, /chat/new) pass `leftSlot`: with no session there
+ * IS no session data (the summary would just read "—"), so that left side
+ * is free real estate — the knowledge-context budget slider lives there
+ * (vision-context-injection.md), vertically centered beside the model +
+ * thinking pills by the row's own geometry.
+ *
* All folds come from core/prices.ts (sessionCost, contextWindow, turnCost)
* — no cost math lives here. Absent data renders "—", never zero (the
* doctrine): no turns yet → every figure is "—"; turns the price table
@@ -26,11 +32,15 @@ export function StatsBar({
chatId,
settings,
onSettingsSaved,
+ leftSlot,
}: {
turns: TurnUsage[];
chatId: string;
settings?: ChatSettings;
onSettingsSaved: (settings: ChatSettings) => void;
+ /** Pre-chat only: replaces the (empty) session-data summary — home and
+ * the /chat/new draft mount the knowledge-context slider here. */
+ leftSlot?: ReactNode;
}) {
const [open, setOpen] = useState(false);
// Same dropdown discipline as the model control (owner, df4433f treatment,
@@ -74,6 +84,9 @@ export function StatsBar({
outside-close ref anchors HERE too (owner, round 3): on the whole
bar it wrapped the model control, so clicking model/thinking never
counted as outside — the asymmetry the owner observed. */}
+ {/* The left side: the session-data summary, or — pre-chat — the
+ caller's slot (the knowledge-context slider) in its place. */}
+ {leftSlot ?? (
+ )}
{/* The model + thinking control — one controller for the conversation
agents (prober/teacher); the record's `turns` above still folds
the ACTUAL models billed, regardless of what the picker says. */}
diff --git a/tests/render-new-session.test.tsx b/tests/render-new-session.test.tsx
index 736f962..24aa698 100644
--- a/tests/render-new-session.test.tsx
+++ b/tests/render-new-session.test.tsx
@@ -84,7 +84,7 @@ describe("DraftShell — the sessionless surface", () => {
assert.ok(/rounded-2xl[^>]*pointer-events-auto|pointer-events-auto[^>]*rounded-2xl/.test(html));
});
- it("renders the knowledge-context slider at the decided default (absent settings = on, 1024)", () => {
+ it("renders the knowledge-context slider in the stats bar's left slot, at the decided default (absent settings = on, 1024)", () => {
const html = renderToStaticMarkup(
{
/>,
);
assert.ok(html.includes('data-testid="graph-context-control"'));
+ // INSIDE the stats row (the session-data slot), not a strip above the
+ // composer: the control renders after the stats bar opens.
+ assert.ok(
+ html.indexOf('data-testid="stats-bar"') < html.indexOf('data-testid="graph-context-control"'),
+ "the slider is the stats bar's left slot",
+ );
// The slider's default position: 1024 of 4096, step 128 (decided).
assert.ok(html.includes('min="0"') && html.includes('max="4096"') && html.includes('step="128"'));
- assert.ok(html.includes("1k tokens"), "the readout shows the default budget, not 0 — default is ON");
+ // The abbreviated readout (owner: "tokens" wrapped at >1k — "tok" stays one line).
+ assert.ok(html.includes("1k tok"), "the readout shows the default budget, not 0 — default is ON");
assert.ok(html.includes("Knowledge context"), "the control is labeled");
// Absent settings render the default — never 'off'.
assert.ok(!html.includes(">off<"));