feat: complete birth time consultation handoff
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
# Private Consultation Entrypoints Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Keep homepage entrypoint copy short and public while expanding the actual consultation instructions only on the server, and make each entry card one responsive click target.
|
||||
|
||||
**Architecture:** The client sends an optional closed `entrypoint` enum beside the visible `question`. A server-only resolver expands trusted entrypoints before constructing Agent and calculation-tool input; persisted history continues to use the public question. Existing card articles gain a stretched native button rather than nested action buttons.
|
||||
|
||||
**Tech Stack:** Next.js 16 App Router, React 19, TypeScript, Zod, Node test runner, token-driven CSS.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Public composer/history copy is exactly `深入看今日`, `生时校正`, or `再次校正`.
|
||||
- Internal entrypoint instructions must not remain in `frontend/src/app/page.tsx` or the browser bundle.
|
||||
- Ordinary typed questions, billing, undo, streaming, and persistence behavior must not change.
|
||||
- Do not assert natural-language prompt prose in tests; assert routing and data ownership.
|
||||
- Use existing `DESIGN.md` tokens and 44px keyboard-accessible card controls.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Server-owned entrypoint resolver
|
||||
|
||||
**Files:**
|
||||
- Create: `frontend/src/lib/consultation-entrypoint.ts`
|
||||
- Create: `frontend/tests/consultation-entrypoint.test.ts`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `consultationEntrypointSchema` and `resolveConsultationQuestion({ entrypoint, visibleQuestion, name, currentDate })`.
|
||||
- Returns: `{ kind: "plain" | "expanded"; modelQuestion: string }`.
|
||||
|
||||
- [x] **Step 1: Write the failing routing tests**
|
||||
|
||||
```ts
|
||||
assert.equal(resolveConsultationQuestion({ entrypoint: undefined, visibleQuestion: "普通问题", name: "林遥", currentDate: "2026-07-19" }).kind, "plain");
|
||||
const daily = resolveConsultationQuestion({ entrypoint: "daily_starlanguage", visibleQuestion: "深入看今日", name: "林遥", currentDate: "2026-07-19" });
|
||||
assert.equal(daily.kind, "expanded");
|
||||
assert.notEqual(daily.modelQuestion, "深入看今日");
|
||||
```
|
||||
|
||||
- [x] **Step 2: Run the focused test and confirm RED**
|
||||
|
||||
Run: `/opt/homebrew/bin/node --test tests/consultation-entrypoint.test.ts`
|
||||
|
||||
Expected: FAIL because the resolver module does not exist.
|
||||
|
||||
- [x] **Step 3: Implement the strict enum and resolver**
|
||||
|
||||
Use a Zod enum for `daily_starlanguage` and `birth_time_rectification`. Keep template strings in this server-imported module and return the visible question unchanged only for the plain branch.
|
||||
|
||||
- [x] **Step 4: Run the focused test and confirm GREEN**
|
||||
|
||||
Run: `/opt/homebrew/bin/node --test tests/consultation-entrypoint.test.ts`
|
||||
|
||||
Expected: all entrypoint routing tests pass.
|
||||
|
||||
### Task 2: Consultation API and client state
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/app/api/consult/route.ts`
|
||||
- Modify: `frontend/src/app/page.tsx`
|
||||
- Modify: `frontend/tests/consultation-entrypoint.test.ts`
|
||||
- Modify: `frontend/tests/sidebar-contract.test.ts`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: optional `entrypoint` enum and `resolveConsultationQuestion`.
|
||||
- Produces: Agent/tool requests using `modelQuestion`, while `userSession.messages` keeps `question`.
|
||||
|
||||
- [x] **Step 1: Add failing API/client ownership tests**
|
||||
|
||||
Assert structurally that the route schema includes the optional enum, tool input overrides `question` with `modelQuestion`, and the page contains no `buildDailyStarlanguageQuestion` or `buildBirthTimeRectificationQuestion` functions.
|
||||
|
||||
- [x] **Step 2: Run the focused tests and confirm RED**
|
||||
|
||||
Run: `/opt/homebrew/bin/node --test tests/consultation-entrypoint.test.ts tests/sidebar-contract.test.ts`
|
||||
|
||||
- [x] **Step 3: Wire the server expansion**
|
||||
|
||||
Resolve once after validation. Use the expanded question for `consultationInputSchema.parse({ ...parsed.data, question: resolved.modelQuestion })` and the final Agent user content. Keep prompt-extraction checks and history based on user-controlled visible text.
|
||||
|
||||
- [x] **Step 4: Wire the client entrypoint state**
|
||||
|
||||
Add `draftEntrypoint` to composer state and `PendingConsultation`. Card selection sets it; textarea edits clear it; sending includes it and clears it; undo restores it. Keep optimistic/persisted `Message.text` equal to the visible composer question.
|
||||
|
||||
- [x] **Step 5: Run the focused tests and confirm GREEN**
|
||||
|
||||
Run: `/opt/homebrew/bin/node --test tests/consultation-entrypoint.test.ts tests/sidebar-contract.test.ts`
|
||||
|
||||
### Task 3: Whole-card interaction
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/app/page.tsx`
|
||||
- Modify: `frontend/src/app/globals.css`
|
||||
- Modify: `frontend/DESIGN.md` only if the existing entrypoint-card contract cannot express the stretched action.
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: card selection helpers from Task 2.
|
||||
- Produces: two semantic articles, each with one absolute inset native button and one lower-right visual action label.
|
||||
|
||||
- [x] **Step 1: Add a failing source contract**
|
||||
|
||||
Assert that both card articles contain a dedicated whole-card action and no nested visible action button inside `.daily-starlanguage-heading`.
|
||||
|
||||
- [x] **Step 2: Run the source contract and confirm RED**
|
||||
|
||||
Run: `/opt/homebrew/bin/node --test tests/sidebar-contract.test.ts`
|
||||
|
||||
- [x] **Step 3: Implement whole-card markup and token-driven states**
|
||||
|
||||
Keep `article`, `dl`, and explanatory copy. Add a stretched button with an accessible name; render the visible action text in the lower-right. Add hover, active, focus-visible, disabled, and reduced-motion states using existing tokens.
|
||||
|
||||
- [x] **Step 4: Run the source contract and confirm GREEN**
|
||||
|
||||
Run: `/opt/homebrew/bin/node --test tests/sidebar-contract.test.ts`
|
||||
|
||||
### Task 4: Regression and real-browser QA
|
||||
|
||||
**Files:**
|
||||
- Modify only files required by observed regressions.
|
||||
|
||||
- [x] **Step 1: Run the full frontend suite**
|
||||
|
||||
Run: `/opt/homebrew/bin/node --test tests/*.test.ts`
|
||||
|
||||
Expected: all tests pass.
|
||||
|
||||
- [x] **Step 2: Run lint and production build**
|
||||
|
||||
Run: `/opt/homebrew/bin/node node_modules/eslint/bin/eslint.js .`
|
||||
|
||||
Run: `/opt/homebrew/bin/node node_modules/next/dist/bin/next build --webpack`
|
||||
|
||||
Expected: zero lint errors and a successful production build.
|
||||
|
||||
- [x] **Step 3: Browser QA at desktop and 390px**
|
||||
|
||||
Use `?preview=birth-time-candidate-complete`. Click each full card and verify the composer contains only the short public text. Edit the composer and verify the request becomes ordinary. Verify focus ring, disabled behavior, CJK wrapping, no large middle button, and profile result visibility.
|
||||
|
||||
- [x] **Step 4: Verify request ownership**
|
||||
|
||||
Inspect the browser request body: it may contain only the public `question` plus the enum. Confirm the internal template is absent from page source, DOM, transcript, and persisted message objects.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Private Consultation Entrypoints Design
|
||||
|
||||
## Goal
|
||||
|
||||
Make the “今日星语” and “生时校正” cards behave like concise product entrypoints without exposing internal consultation instructions in the composer, transcript, browser bundle, or persisted chat history.
|
||||
|
||||
## Approved interaction
|
||||
|
||||
- Clicking anywhere on the 今日星语 card places `深入看今日` in the composer.
|
||||
- Clicking the 生时校正 card places `生时校正` before a result exists and `再次校正` after a candidate or confirmed time exists.
|
||||
- Each card remains a semantic `article`; one stretched native button covers the card, and a quiet action label with an arrow sits at the lower-right edge.
|
||||
- The cards contain no nested visible button, so mobile layouts do not create a large control in the middle of the content.
|
||||
- Keyboard focus, disabled state, and the 44px interaction requirement apply to the whole-card button.
|
||||
|
||||
## Request contract
|
||||
|
||||
The browser may send one optional, closed entrypoint value:
|
||||
|
||||
```ts
|
||||
type ConsultationEntrypoint =
|
||||
| "daily_starlanguage"
|
||||
| "birth_time_rectification";
|
||||
```
|
||||
|
||||
The visible `question` remains the exact short sentence displayed to the user. The server validates the optional entrypoint and expands it into the internal model question. Arbitrary template names or client-authored prompt content are rejected by schema validation.
|
||||
|
||||
Normal typed questions omit `entrypoint` and retain their current behavior.
|
||||
|
||||
## Composer and transcript behavior
|
||||
|
||||
- Card selection stores the short visible question plus an internal entrypoint enum in React state.
|
||||
- Any manual edit to the composer clears the entrypoint enum, so edited text is treated as an ordinary user question.
|
||||
- Sending clears both fields.
|
||||
- Undo/cancel restores both fields, preserving the same behavior when the user retries.
|
||||
- Optimistic messages, persisted chat sessions, sharing, and future conversation history store only the visible short question.
|
||||
|
||||
## Server behavior
|
||||
|
||||
- A server-only resolver owns both internal prompt templates.
|
||||
- `daily_starlanguage` expands with authoritative server time, the validated birth/chart input, the requested daily sections, and the existing non-deterministic boundary.
|
||||
- `birth_time_rectification` expands with the validated birth/chart input, a request to continue or restart evidence-led rectification, and the existing rule that a candidate is not a proven birth minute.
|
||||
- The expanded question is used both in the final Agent message and in `consultationInputSchema` passed to the calculation tool.
|
||||
- User-visible history remains unchanged and never receives the expanded text.
|
||||
- Prompt-extraction safety continues to inspect user-controlled visible content; the trusted server expansion is not treated as user input.
|
||||
|
||||
## Failure behavior
|
||||
|
||||
- Unknown entrypoint values return the existing 400 invalid-request response before billing.
|
||||
- A valid entrypoint with an edited visible sentence is impossible through the product UI because editing clears the enum; the server still treats the enum as authoritative if a direct API client supplies both.
|
||||
- Billing, cancellation settlement, streaming, model selection, and ordinary questions retain their current paths.
|
||||
|
||||
## Verification
|
||||
|
||||
- Unit tests prove each entrypoint selects a server expansion without pinning natural-language prompt prose.
|
||||
- Route contract tests prove the enum is optional, invalid values fail, and the expanded question reaches both Agent and tool input.
|
||||
- Client tests prove the browser source no longer contains the internal daily or rectification prompt builders, manual editing clears intent, and cancellation restores it.
|
||||
- Browser QA verifies whole-card click, short composer text, keyboard focus, and desktop/390px mobile layout.
|
||||
Reference in New Issue
Block a user