- Sidebar: the three-way kind filter (Teaching/All/Practice, persisted
in localStorage) above the search; two new-session doors (Teaching →
/chat/new, Practice → /chat/new?mode=practice); practice rows carry
the dumbbell badge and 'Practicing…' busy meta.
- Draft: ?mode=practice swaps the copy (title, empty state, 'What do
you want to practice?') and startDraftSession posts kind:'practice'.
- Graph entry (home composer): teach|practice switch on the stats row's
left edge (StatsBar leadingSlot); practice mode shows the propositions
chip that opens the chain table as a picker (its floating bar becomes
'Practice these' — no delete fence); the picks seed the opening prompt
('Propositions from the map:…') while the title stays the typed text.
31 lines
1.1 KiB
TypeScript
31 lines
1.1 KiB
TypeScript
import { NewSession } from "@/components/new-session";
|
|
import { readSettingsRecord } from "@/src/server";
|
|
import { slotsConfigured } from "@/src/core";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
/**
|
|
* The draft view behind "+ New session" (owner, 2026-09-13): NO session is
|
|
* created until the first message is sent — the view is a sessionless empty
|
|
* chat; on send it creates the chat (title = that message) and redirects to
|
|
* the real page, whose initialPrompt path delivers the message as turn 1.
|
|
* Static segment beats the dynamic [id] route — "new" is never a chat id.
|
|
*
|
|
* The draft's model control binds the STRONG SLOT (the new-session
|
|
* default); the session the first send creates is stamped from it
|
|
* server-side (per-session models, owner 2026-09-28).
|
|
*/
|
|
export default async function NewSessionPage({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ mode?: string }>;
|
|
}) {
|
|
const { mode } = await searchParams;
|
|
return (
|
|
<NewSession
|
|
initialConfigured={slotsConfigured(readSettingsRecord()?.models)}
|
|
mode={mode === "practice" ? "practice" : "teach"}
|
|
/>
|
|
);
|
|
}
|