Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 1 addition & 85 deletions packages/app/src/pages/new-session.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { createPromptProjectController } from "@/components/prompt-project-selector"
import { useTitlebarRightMount } from "@/components/titlebar"
import { useSettings } from "@/context/settings"
import { createEffect, createMemo, createResource, onMount } from "solid-js"
import { createEffect, createResource, onMount } from "solid-js"
import { useLocation } from "@solidjs/router"
import { createNewSessionDraftController } from "./new-session/new-session-draft-controller"
import { NewSessionStatus, NewSessionView } from "./new-session/new-session-view"
import { createNewSessionWorkspaceController } from "./new-session/new-session-workspace-controller"
import { useNewSessionCommands } from "./new-session/use-new-session-commands"
import { usePrompt } from "@/context/prompt"
import { useServer } from "@/context/server"
import { startPrompt as startPromptWith } from "@/utils/start-prompt"
import { postRouteInfo } from "@/utils/amicode-route-info"
import { amicodeGet } from "@/utils/amicode-fetch"
import { parseProblemsResponse } from "@opencode-ai/ui/amicode-problem-switcher"
import { AmicodeStarterChips, AMICODE_STARTERS, type StarterChip } from "@opencode-ai/ui/amicode-getting-started"
import { useAmicodeCommands } from "@/pages/session/use-amicode-commands"

/** The draft-only V2 session page. Submitting promotes the draft into a real session. */
Expand Down Expand Up @@ -42,77 +36,6 @@ export default function NewSessionPage() {
// palette, so restart/update-memory are reachable via their direct keybinds.
useAmicodeCommands()

// amicode: starter chips fill this page's composer and submit (start-prompt.ts).
const prompt = usePrompt()
const startPrompt = (text: string) => startPromptWith(prompt, text)

// amicode: Resume verb (spec B) — most-recent non-archived problem workspace,
// or undefined (no chip) on fetch failure / empty. Mirrors session-new-view.
const server = useServer()
const [problemsRaw] = createResource(() => amicodeGet(server.current, "/amicode/problems").catch(() => undefined))
const resumeProblem = createMemo(() => {
const raw = problemsRaw()
if (raw === undefined) return undefined
const view = parseProblemsResponse(raw)
if (!view.ok) return undefined
const open = view.problems.filter((p) => p.status !== "archived")
if (open.length === 0) return undefined
return [...open].sort((a, b) => (b.recorded ?? "").localeCompare(a.recorded ?? ""))[0]
})

// amicode: dynamic chip ranking (2026-08-01 — chips under the composer,
// earned not static). Problem workspaces are all status "designing", so the
// rank keys off recency + entity_kinds: a `pulse` entity means a banked
// pulse (warm-startable); a `run` without a `pulse` means the attempt
// stalled (retry-able); anything recent is resume-able. Capped at 4, padded
// to 3 with the static starters when history is thin, static-only when
// there's no history at all.
const chips = createMemo((): StarterChip[] => {
const raw = problemsRaw()
if (raw === undefined) return AMICODE_STARTERS.map((s) => ({ label: s.label, prompt: s.prompt }))
const view = parseProblemsResponse(raw)
if (!view.ok) return AMICODE_STARTERS.map((s) => ({ label: s.label, prompt: s.prompt }))
const open = [...view.problems.filter((p) => p.status !== "archived")].sort((a, b) =>
(b.recorded ?? "").localeCompare(a.recorded ?? ""),
)
const out: StarterChip[] = []
const seen = new Set<string>()
const resume = open[0]
if (resume) {
out.push({
label: `Resume ${resume.name}`,
prompt: `Open the problem "${resume.name}" and continue where we left off`,
dataSlot: "amicode-gs-resume",
})
seen.add(resume.slug)
}
const banked = open.find((p) => !seen.has(p.slug) && p.entityKinds.includes("pulse"))
if (banked) {
out.push({
label: `Warm-start from ${banked.name}`,
prompt: `Warm-start a new solve from the banked pulse on "${banked.name}"`,
dataSlot: "amicode-gs-warmstart",
})
seen.add(banked.slug)
}
const stalled = open.find(
(p) => !seen.has(p.slug) && p.entityKinds.includes("run") && !p.entityKinds.includes("pulse"),
)
if (stalled) {
out.push({
label: `Retry ${stalled.name}`,
prompt: `Pick back up "${stalled.name}" — the last attempt stalled without a banked pulse. Diagnose what happened and try again`,
dataSlot: "amicode-gs-retry",
})
seen.add(stalled.slug)
}
for (const starter of AMICODE_STARTERS) {
if (out.length >= 3) break
out.push({ label: starter.label, prompt: starter.prompt })
}
return out.slice(0, 4)
})

createEffect(() => {
if (!draft.prompt.ready()) return
draft.input.restoreFocus()
Expand All @@ -134,17 +57,10 @@ export default function NewSessionPage() {
{suspendUntilPromptReady()}
<NewSessionStatus mount={rightMount} visible={settings.visibility.status} />
<div class="flex-1 min-h-0 flex flex-col gap-2 p-2">
{/*
amicode: chips ride inside the hero view now (Kimi ordering — brand →
composer → chips), ranked from the problem history: Resume →
Warm-start → Retry → static pad. They persist while typing and
disappear on submit, when this page navigates to the real session.
*/}
<NewSessionView
input={draft.input}
project={project}
workspace={workspace}
gettingStarted={<AmicodeStarterChips onStart={startPrompt} chips={chips()} />}
/>
</div>
</div>
Expand Down
8 changes: 1 addition & 7 deletions packages/app/src/pages/new-session/new-session-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { WordmarkV2 } from "@opencode-ai/ui/v2/wordmark-v2"
import { MarkDetailed } from "@opencode-ai/ui/logo"
import { Show, createMemo, createSignal, type Accessor, type JSX } from "solid-js"
import { Show, createMemo, createSignal, type Accessor } from "solid-js"
import { createStore } from "solid-js/store"
import { Portal } from "solid-js/web"
import createPresence from "solid-presence"
Expand Down Expand Up @@ -33,9 +33,6 @@ export function NewSessionView(props: {
input: NewSessionDraftController["input"]
project: PromptProjectController
workspace: NewSessionWorkspaceController
// amicode: starter chips slot (Kimi-clean ordering — brand → composer →
// chips → project row). Optional: the classic view renders nothing.
gettingStarted?: JSX.Element
}) {
return (
<div class="@container relative flex flex-col min-h-0 h-full flex-1">
Expand Down Expand Up @@ -66,9 +63,6 @@ export function NewSessionView(props: {
</Show>
<PromptInputV2Composer controller={props.input} />
</div>
<Show when={props.gettingStarted}>
<div class="flex justify-center -mt-2">{props.gettingStarted}</div>
</Show>
<Show when={props.project.empty()}>
<PromptProjectAddButton controller={props.project} />
</Show>
Expand Down
Loading