story-kit: merge 148_story_interactive_onboarding_guides_user_through_project_setup_after_init

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-24 15:34:31 +00:00
parent 81ac2f309a
commit 5567cdf480
7 changed files with 476 additions and 4 deletions

View File

@@ -63,7 +63,9 @@ export type WsResponse =
| { type: "agent_config_changed" }
| { type: "tool_activity"; tool_name: string }
/** Heartbeat response confirming the connection is alive. */
| { type: "pong" };
| { type: "pong" }
/** Sent on connect when the project still needs onboarding (specs are placeholders). */
| { type: "onboarding_status"; needs_onboarding: boolean };
export interface ProviderConfig {
provider: string;
@@ -280,6 +282,7 @@ export class ChatWebSocket {
message: string,
) => void;
private onAgentConfigChanged?: () => void;
private onOnboardingStatus?: (needsOnboarding: boolean) => void;
private connected = false;
private closeTimer?: number;
private wsPath = DEFAULT_WS_PATH;
@@ -355,6 +358,8 @@ export class ChatWebSocket {
data.message,
);
if (data.type === "agent_config_changed") this.onAgentConfigChanged?.();
if (data.type === "onboarding_status")
this.onOnboardingStatus?.(data.needs_onboarding);
if (data.type === "pong") {
window.clearTimeout(this.heartbeatTimeout);
this.heartbeatTimeout = undefined;
@@ -405,6 +410,7 @@ export class ChatWebSocket {
message: string,
) => void;
onAgentConfigChanged?: () => void;
onOnboardingStatus?: (needsOnboarding: boolean) => void;
},
wsPath = DEFAULT_WS_PATH,
) {
@@ -417,6 +423,7 @@ export class ChatWebSocket {
this.onActivity = handlers.onActivity;
this.onReconciliationProgress = handlers.onReconciliationProgress;
this.onAgentConfigChanged = handlers.onAgentConfigChanged;
this.onOnboardingStatus = handlers.onOnboardingStatus;
this.wsPath = wsPath;
this.shouldReconnect = true;

View File

@@ -88,6 +88,8 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
>([]);
const reconciliationEventIdRef = useRef(0);
const [agentConfigVersion, setAgentConfigVersion] = useState(0);
const [needsOnboarding, setNeedsOnboarding] = useState(false);
const onboardingTriggeredRef = useRef(false);
const wsRef = useRef<ChatWebSocket | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
@@ -237,6 +239,9 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
onAgentConfigChanged: () => {
setAgentConfigVersion((v) => v + 1);
},
onOnboardingStatus: (onboarding: boolean) => {
setNeedsOnboarding(onboarding);
},
});
return () => {
@@ -495,6 +500,63 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
gap: "24px",
}}
>
{needsOnboarding && messages.length === 0 && !loading && (
<div
data-testid="onboarding-welcome"
style={{
padding: "24px",
borderRadius: "12px",
background: "#1c2a1c",
border: "1px solid #2d4a2d",
marginBottom: "8px",
}}
>
<h3
style={{
margin: "0 0 8px 0",
color: "#a0d4a0",
fontSize: "1.1rem",
}}
>
Welcome to Story Kit
</h3>
<p
style={{
margin: "0 0 16px 0",
color: "#ccc",
lineHeight: 1.5,
}}
>
This project needs to be set up before you can start writing
stories. The agent will guide you through configuring your
project goals and tech stack.
</p>
<button
type="button"
data-testid="onboarding-start-button"
onClick={() => {
if (onboardingTriggeredRef.current) return;
onboardingTriggeredRef.current = true;
setNeedsOnboarding(false);
sendMessage(
"I just created a new project. Help me set it up.",
);
}}
style={{
padding: "10px 20px",
borderRadius: "8px",
border: "none",
backgroundColor: "#a0d4a0",
color: "#1a1a1a",
cursor: "pointer",
fontSize: "0.95rem",
fontWeight: 600,
}}
>
Start Project Setup
</button>
</div>
)}
{messages.map((msg: Message, idx: number) => (
<div
key={`msg-${idx}-${msg.role}-${msg.content.substring(0, 20)}`}