Prioritize Dream QA Today Tip card

#21
by ADJCJH - opened
PRODUCT.md CHANGED
@@ -14,7 +14,7 @@ The product helps a user unpack a dream through a guided, step-by-step Q&A. It a
14
 
15
  The hackathon-facing experience is English-first. A visible `English / 中文` language control keeps the Chinese experience available without making judges start from a Chinese-only first screen.
16
 
17
- Success means the user receives something more useful than a one-shot interpretation: a short summary of the dream, one clear question they were trying to answer, a non-certain explanation grounded in concrete dream details, and a tiny suggestion for today. The tip can be practical, playful, or simply caring, but it must feel connected to the dream.
18
 
19
  ## Brand Personality
20
 
@@ -44,6 +44,7 @@ The voice is a calm dream companion who asks good questions and takes odd detail
44
  8. Recommendation quality is tested with a deterministic Today Tip eval set before deployment.
45
  9. English mode must stay natural English, including translated or paraphrased dream anchors; mixed Chinese helper text is a regression.
46
  10. Tiny actions should feel like a first step the user may choose, not a hard command to solve the real-world problem immediately.
 
47
 
48
  ## Accessibility & Inclusion
49
 
 
14
 
15
  The hackathon-facing experience is English-first. A visible `English / 中文` language control keeps the Chinese experience available without making judges start from a Chinese-only first screen.
16
 
17
+ Success means the user receives something more useful than a one-shot interpretation: one clear today tip first, an optional tiny action, and then a non-certain explanation grounded in concrete dream details and the user's answer. The tip can be practical, playful, or simply caring, but it must feel connected to the dream.
18
 
19
  ## Brand Personality
20
 
 
44
  8. Recommendation quality is tested with a deterministic Today Tip eval set before deployment.
45
  9. English mode must stay natural English, including translated or paraphrased dream anchors; mixed Chinese helper text is a regression.
46
  10. Tiny actions should feel like a first step the user may choose, not a hard command to solve the real-world problem immediately.
47
+ 11. The final card prioritizes the Today Tip before interpretation; interpretation is supporting reflection, not the main payoff.
48
 
49
  ## Accessibility & Inclusion
50
 
README.md CHANGED
@@ -30,7 +30,7 @@ The public hackathon demo is English-first for international judges. A visible i
30
 
31
  ## Concept
32
 
33
- Dream QA accepts dream fragments by text, image, or voice. It turns those fragments into a shared dream intake, asks a grounded follow-up question, and returns a Today Tip card: a short non-certain interpretation, one practical or caring suggestion for today, and an optional tiny action.
34
 
35
  This is not a therapy, diagnosis, or prophecy product.
36
 
@@ -48,8 +48,8 @@ This is not a therapy, diagnosis, or prophecy product.
48
  1. Enter a dream by text, image, voice, or a mix.
49
  2. State or let the app infer the main question.
50
  3. Answer or skip one or more follow-up questions.
51
- 4. Review a grounded interpretation.
52
- 5. Receive one Today Tip tied to concrete dream details.
53
 
54
  ## Language
55
 
 
30
 
31
  ## Concept
32
 
33
+ Dream QA accepts dream fragments by text, image, or voice. It turns those fragments into a shared dream intake, asks a grounded follow-up question, and returns a Today Tip card: one practical or caring suggestion first, an optional tiny action, and a short non-certain supporting reflection.
34
 
35
  This is not a therapy, diagnosis, or prophecy product.
36
 
 
48
  1. Enter a dream by text, image, voice, or a mix.
49
  2. State or let the app infer the main question.
50
  3. Answer or skip one or more follow-up questions.
51
+ 4. Receive one Today Tip tied to concrete dream details and the user's answer.
52
+ 5. Read the supporting reflection if the user wants more context.
53
 
54
  ## Language
55
 
docs/handoff.md CHANGED
@@ -56,6 +56,7 @@ Final positioning:
56
  - English mode is not just English UI chrome; generated anchors, questions, and Today Tips should read as natural English.
57
  - If the model receives Chinese anchors while the selected language is English, translate or paraphrase them unless a short exact quote is intentionally preserved.
58
  - Tiny actions should feel like five-minute first steps. Avoid urgent command phrasing such as "address it immediately."
 
59
 
60
  ## Model Decision
61
 
 
56
  - English mode is not just English UI chrome; generated anchors, questions, and Today Tips should read as natural English.
57
  - If the model receives Chinese anchors while the selected language is English, translate or paraphrase them unless a short exact quote is intentionally preserved.
58
  - Tiny actions should feel like five-minute first steps. Avoid urgent command phrasing such as "address it immediately."
59
+ - The final card should show the Today Tip before interpretation; interpretation is supporting reflection and should use the user's answer when it names a real task.
60
 
61
  ## Model Decision
62
 
docs/prd.md CHANGED
@@ -98,11 +98,13 @@ As a user, I want a clear final result so that I leave with one small thing for
98
 
99
  Acceptance criteria:
100
 
101
- - Result includes dream summary, main question, interpretation, today tip, optional tiny action, optional caring note, and safety note when needed.
 
102
  - Result is readable on mobile and desktop.
103
  - Result avoids medical, prophetic, or deterministic framing.
104
  - Today tip references a concrete dream anchor.
105
  - Optional tiny action is a five-minute first step, not a command to solve the whole issue immediately.
 
106
 
107
  #### Story 6: Bilingual Demo
108
 
 
98
 
99
  Acceptance criteria:
100
 
101
+ - Result includes dream summary, main question, today tip, optional tiny action, supporting interpretation, optional caring note, and safety note when needed.
102
+ - Result presents the Today Tip before supporting interpretation.
103
  - Result is readable on mobile and desktop.
104
  - Result avoids medical, prophetic, or deterministic framing.
105
  - Today tip references a concrete dream anchor.
106
  - Optional tiny action is a five-minute first step, not a command to solve the whole issue immediately.
107
+ - Supporting interpretation uses the user's answer when the answer names a concrete waking-life task.
108
 
109
  #### Story 6: Bilingual Demo
110
 
docs/smoke/2026-06-10-tip-first-card-smoke.md ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dream QA Tip-First Card Smoke - 2026-06-10
2
+
3
+ ## Scope
4
+
5
+ - Put the final Today Tip before supporting interpretation.
6
+ - Make supporting interpretation use the user's waking-life answer when it names a concrete task.
7
+ - Preserve English-only output quality and the existing Chinese toggle.
8
+
9
+ ## TDD Red
10
+
11
+ Focused red commands:
12
+
13
+ ```text
14
+ .venv/bin/python -m pytest tests/test_render.py tests/test_ui_actions.py tests/test_today_tip_quality_eval.py -q
15
+ .venv/bin/python scripts/evaluate_today_tip_quality.py
16
+ ```
17
+
18
+ Observed failures before implementation:
19
+
20
+ ```text
21
+ test_today_tip_card_prioritizes_tip_before_interpretation
22
+ test_english_interpretation_uses_user_answer_before_tip
23
+ test_today_tip_quality_eval_passes_fixture_cases
24
+ elevator_email_first_sentence: missing_interpretation_terms:overdue email
25
+ ```
26
+
27
+ ## Local Verification
28
+
29
+ Commands:
30
+
31
+ ```text
32
+ .venv/bin/python -m pytest -q
33
+ .venv/bin/python scripts/evaluate_today_tip_quality.py
34
+ git diff --check
35
+ ```
36
+
37
+ Observed:
38
+
39
+ ```text
40
+ 97 passed, 2 warnings
41
+ {"case_count": 11, "failures": {}, "passes": true}
42
+ git diff --check clean
43
+ ```
44
+
45
+ ## Local Gradio Config
46
+
47
+ Observed from `http://127.0.0.1:7860/config`:
48
+
49
+ ```text
50
+ title=Dream QA
51
+ version=4.44.1
52
+ mode=blocks
53
+ component_count=72
54
+ ```
55
+
56
+ ## Local Action Smoke
57
+
58
+ English dream:
59
+
60
+ ```text
61
+ I dreamed I was in an elevator where the floor buttons melted like wax. The number 14 kept blinking, and I felt late but strangely calm.
62
+ ```
63
+
64
+ English answer:
65
+
66
+ ```text
67
+ I want to make starting my overdue email easier without feeling trapped by it.
68
+ ```
69
+
70
+ Observed:
71
+
72
+ ```text
73
+ status=tip
74
+ title=Today Tip
75
+ tip_before_reflection=True
76
+ interpretation_has_email=True
77
+ interpretation_has_floor14=True
78
+ has_chinese_leakage=False
79
+ has_immediately=False
80
+ ```
81
+
82
+ ## Hugging Face Space Sync
83
+
84
+ Pending.
85
+
86
+ ## Public User-View Review
87
+
88
+ Pending.
docs/spec.md CHANGED
@@ -176,9 +176,9 @@ The final result must include:
176
 
177
  - Dream summary.
178
  - Main question.
179
- - Grounded interpretation.
180
  - One primary today tip.
181
  - Optional tiny action.
 
182
  - Optional caring note.
183
  - Safety note if the input indicates severe distress.
184
 
@@ -191,6 +191,8 @@ The final result must:
191
  - Use English UI/output labels by default and Chinese labels only when `中文` is selected.
192
  - Keep English mode fully English unless preserving a deliberate short user quote.
193
  - Make the optional tiny action a five-minute first step, not a demand to complete the whole waking-life problem.
 
 
194
 
195
  ## 10. UX Structure
196
 
 
176
 
177
  - Dream summary.
178
  - Main question.
 
179
  - One primary today tip.
180
  - Optional tiny action.
181
+ - Supporting grounded interpretation.
182
  - Optional caring note.
183
  - Safety note if the input indicates severe distress.
184
 
 
191
  - Use English UI/output labels by default and Chinese labels only when `中文` is selected.
192
  - Keep English mode fully English unless preserving a deliberate short user quote.
193
  - Make the optional tiny action a five-minute first step, not a demand to complete the whole waking-life problem.
194
+ - Render the Today Tip before the interpretation so the user sees the actionable payoff first.
195
+ - When the user answer names a real task, the supporting interpretation should bridge a dream anchor to that task.
196
 
197
  ## 10. UX Structure
198
 
docs/superpowers/plans/2026-06-10-dream-qa-tip-first-card.md ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dream QA Tip-First Card Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Put the final Today Tip before interpretation and make the interpretation paragraph use the user's waking-life answer.
6
+
7
+ **Architecture:** Keep the existing `TodayTipCard` schema and Dream QA flow. Change HTML order in `render_today_tip_card`, add answer-aware interpretation polish in `pipeline.py`, and guard both with focused tests and a public Space smoke.
8
+
9
+ **Tech Stack:** Python, Pydantic, Gradio, pytest, Hugging Face Space.
10
+
11
+ ---
12
+
13
+ ## File Map
14
+
15
+ - Modify `dream_customs/render.py`: render the primary Today Tip before interpretation, with interpretation as a quieter details block.
16
+ - Modify `dream_customs/pipeline.py`: when answers mention a concrete waking-life task such as email/message, make interpretation bridge the dream anchor to that answer.
17
+ - Modify `tests/test_render.py`: assert the rendered HTML puts the primary Today Tip before the interpretation block.
18
+ - Modify `tests/test_ui_actions.py`: assert the final card's interpretation references the user's email/message answer.
19
+ - Modify `scripts/evaluate_today_tip_quality.py` and `tests/fixtures/today_tip_eval_cases.json`: add optional answer-term quality checks for cases where the user gives a concrete answer.
20
+ - Modify `README.md`, `PRODUCT.md`, `docs/spec.md`, `docs/prd.md`, `docs/handoff.md`: record the tip-first output hierarchy and answer-aware interpretation rule.
21
+ - Add `docs/smoke/2026-06-10-tip-first-card-smoke.md`: record local, HF, and browser user-view verification.
22
+
23
+ ---
24
+
25
+ ### Task 1: Tests First
26
+
27
+ **Files:**
28
+ - Modify: `tests/test_render.py`
29
+ - Modify: `tests/test_ui_actions.py`
30
+ - Modify: `scripts/evaluate_today_tip_quality.py`
31
+ - Modify: `tests/fixtures/today_tip_eval_cases.json`
32
+
33
+ - [x] **Step 1: Add render order test**
34
+
35
+ Add a test that renders a `TodayTipCard` and asserts `Today's small suggestion` appears before `Maybe this dream is pointing to`.
36
+
37
+ - [x] **Step 2: Add answer-aware interpretation test**
38
+
39
+ Add a UI action test using the elevator/email flow and assert the final card text contains `overdue email` in the interpretation or copyable card text before the Tiny action.
40
+
41
+ - [x] **Step 3: Add answer term eval**
42
+
43
+ Extend the deterministic evaluator so a fixture may include `required_answer_terms`, and fail if those terms are missing from English output.
44
+
45
+ - [x] **Step 4: Run focused red tests**
46
+
47
+ Run:
48
+
49
+ ```bash
50
+ .venv/bin/python -m pytest tests/test_render.py tests/test_ui_actions.py tests/test_today_tip_quality_eval.py -q
51
+ .venv/bin/python scripts/evaluate_today_tip_quality.py
52
+ ```
53
+
54
+ Expected before implementation: at least one focused failure for card order or answer-aware interpretation.
55
+
56
+ ### Task 2: Implement Tip-First Output
57
+
58
+ **Files:**
59
+ - Modify: `dream_customs/render.py`
60
+ - Modify: `dream_customs/pipeline.py`
61
+
62
+ - [x] **Step 1: Reorder result card HTML**
63
+
64
+ Move the primary Today Tip article before the interpretation article. Keep Tiny action after the main suggestion.
65
+
66
+ - [x] **Step 2: Quiet the interpretation block**
67
+
68
+ Keep interpretation visible, but label it as supporting reflection/details rather than the first thing a user sees.
69
+
70
+ - [x] **Step 3: Add answer-aware interpretation polish**
71
+
72
+ When the user's answer includes email/message language, set the English interpretation to bridge the dream anchor to starting that email/message gently.
73
+
74
+ - [x] **Step 4: Run focused green tests**
75
+
76
+ Run:
77
+
78
+ ```bash
79
+ .venv/bin/python -m pytest tests/test_render.py tests/test_ui_actions.py tests/test_today_tip_quality_eval.py -q
80
+ .venv/bin/python scripts/evaluate_today_tip_quality.py
81
+ ```
82
+
83
+ Expected: focused tests pass.
84
+
85
+ ### Task 3: Docs, Smoke, Deploy
86
+
87
+ **Files:**
88
+ - Modify: `README.md`
89
+ - Modify: `PRODUCT.md`
90
+ - Modify: `docs/spec.md`
91
+ - Modify: `docs/prd.md`
92
+ - Modify: `docs/handoff.md`
93
+ - Add: `docs/smoke/2026-06-10-tip-first-card-smoke.md`
94
+ - Modify: this plan file checkbox state.
95
+
96
+ - [x] **Step 1: Align product docs**
97
+
98
+ Record that the final screen prioritizes Today Tip first, then optional supporting interpretation.
99
+
100
+ - [x] **Step 2: Run full local verification**
101
+
102
+ Run:
103
+
104
+ ```bash
105
+ .venv/bin/python -m pytest -q
106
+ .venv/bin/python scripts/evaluate_today_tip_quality.py
107
+ git diff --check
108
+ ```
109
+
110
+ Expected: all commands pass.
111
+
112
+ - [x] **Step 3: Run local config/action smoke**
113
+
114
+ Start the local app, check `/config`, and run the elevator/email action flow.
115
+
116
+ - [ ] **Step 4: Commit and push**
117
+
118
+ Commit implementation, docs, plan, and smoke evidence. Push `feature/dream-qa-tip-first-card`.
119
+
120
+ - [ ] **Step 5: Merge GitHub and HF Space**
121
+
122
+ Create/merge GitHub PR. Upload to HF Space with `create_pr=True`, use Chrome to click Merge, then verify public `/config`.
123
+
124
+ - [ ] **Step 6: Public user-view review**
125
+
126
+ Use the public Space in browser with the elevator/email dream. Confirm Today Tip appears before interpretation and interpretation references the email answer.
127
+
128
+ ---
129
+
130
+ ## Self-Review
131
+
132
+ - Spec coverage: Covers the exact requested next optimization: Today Tip first, interpretation as support, interpretation tied to user answer, docs and deployment.
133
+ - Placeholder scan: No TBD/TODO/fill-later placeholders remain.
134
+ - Type consistency: Reuses existing `TodayTipCard`, `render_today_tip_card`, and UI action APIs without schema changes.
dream_customs/pipeline.py CHANGED
@@ -312,6 +312,31 @@ def _answer_based_today_tip(answers: str, anchor: str, language: str = "en") ->
312
  return ""
313
 
314
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  def _anchor_in_text(text: str, anchors: List[str]) -> bool:
316
  clean = (text or "").lower()
317
  for anchor in anchors:
@@ -594,7 +619,10 @@ def _polish_today_tip(card: TodayTipCard, intake: DreamIntake, answers: str = ""
594
  polished.dream_summary = _summary_from_intake(intake, language)
595
  if not polished.main_question.strip():
596
  polished.main_question = _main_question_from_intake(intake, language)
597
- if not polished.interpretation.strip() or not _anchor_in_text(polished.interpretation, anchors):
 
 
 
598
  polished.interpretation = _fallback_interpretation(intake, language)
599
  generic_tip_markers = ["drink water", "hydrate", "多休息", "保持积极", "take a walk"]
600
  answer_tip = _answer_based_today_tip(answers, anchors[0], language)
 
312
  return ""
313
 
314
 
315
+ def _answer_based_interpretation(answers: str, anchor: str, language: str = "en") -> str:
316
+ lowered = (answers or "").lower()
317
+ if _is_zh(language):
318
+ if "邮件" in lowered or "email" in lowered:
319
+ return f"也许「{anchor}」不是在催你立刻完成什么,而是在提醒你:那封邮件可以先从一句话开始。"
320
+ if "消息" in lowered or "message" in lowered:
321
+ return f"也许「{anchor}」不是在催你立刻回应什么,而是在提醒你:那条消息可以先从一句草稿开始。"
322
+ return ""
323
+ if "email" in lowered:
324
+ return (
325
+ f"Maybe the {anchor} is not asking you to finish the overdue email at once. "
326
+ "It is pointing to the gentler threshold: opening it and writing one first sentence."
327
+ )
328
+ if "message" in lowered:
329
+ return (
330
+ f"Maybe the {anchor} is not asking you to answer every message at once. "
331
+ "It is pointing to the gentler threshold: opening one thread and drafting one first sentence."
332
+ )
333
+ return ""
334
+
335
+
336
+ def _answer_bridge_anchor(anchors: List[str]) -> str:
337
+ return next((anchor for anchor in anchors if "14" in anchor), anchors[0])
338
+
339
+
340
  def _anchor_in_text(text: str, anchors: List[str]) -> bool:
341
  clean = (text or "").lower()
342
  for anchor in anchors:
 
619
  polished.dream_summary = _summary_from_intake(intake, language)
620
  if not polished.main_question.strip():
621
  polished.main_question = _main_question_from_intake(intake, language)
622
+ answer_interpretation = _answer_based_interpretation(answers, _answer_bridge_anchor(anchors), language)
623
+ if answer_interpretation:
624
+ polished.interpretation = answer_interpretation
625
+ elif not polished.interpretation.strip() or not _anchor_in_text(polished.interpretation, anchors):
626
  polished.interpretation = _fallback_interpretation(intake, language)
627
  generic_tip_markers = ["drink water", "hydrate", "多休息", "保持积极", "take a walk"]
628
  answer_tip = _answer_based_today_tip(answers, anchors[0], language)
dream_customs/render.py CHANGED
@@ -33,7 +33,7 @@ def render_today_tip_card(card: TodayTipCard, language: str = "en") -> str:
33
  "thanks": "谢谢你的分享。根据你的梦境,我们为你整理了今天的参考。"
34
  if is_zh
35
  else "Thanks for sharing this. Here is one grounded suggestion for today.",
36
- "interpretation": "也许这个梦在提醒你" if is_zh else "Maybe this dream is pointing to",
37
  "today_tip": "今天的小建议" if is_zh else "Today's small suggestion",
38
  "tiny_action": "没试过的小事" if is_zh else "Tiny action",
39
  "anchors": "关键词:" if is_zh else "Anchors:",
@@ -73,14 +73,6 @@ def render_today_tip_card(card: TodayTipCard, language: str = "en") -> str:
73
  <p>{labels['thanks']}</p>
74
  </div>
75
  </div>
76
- <article class="dqa-result-card is-interpretation">
77
- <div class="dqa-card-icon">☘</div>
78
- <div>
79
- <h3>{labels['interpretation']}</h3>
80
- <p>{escape(card.interpretation)}</p>
81
- <div class="dqa-anchor-strip">{labels['anchors']} {anchors}</div>
82
- </div>
83
- </article>
84
  <article class="dqa-result-card is-primary-tip">
85
  <div class="dqa-card-icon">☼</div>
86
  <div>
@@ -90,6 +82,14 @@ def render_today_tip_card(card: TodayTipCard, language: str = "en") -> str:
90
  </div>
91
  </article>
92
  {tiny_action}
 
 
 
 
 
 
 
 
93
  {qa_history}
94
  {safety}
95
  {caring_note}
 
33
  "thanks": "谢谢你的分享。根据你的梦境,我们为你整理了今天的参考。"
34
  if is_zh
35
  else "Thanks for sharing this. Here is one grounded suggestion for today.",
36
+ "interpretation": "补充解读" if is_zh else "Supporting reflection",
37
  "today_tip": "今天的小建议" if is_zh else "Today's small suggestion",
38
  "tiny_action": "没试过的小事" if is_zh else "Tiny action",
39
  "anchors": "关键词:" if is_zh else "Anchors:",
 
73
  <p>{labels['thanks']}</p>
74
  </div>
75
  </div>
 
 
 
 
 
 
 
 
76
  <article class="dqa-result-card is-primary-tip">
77
  <div class="dqa-card-icon">☼</div>
78
  <div>
 
82
  </div>
83
  </article>
84
  {tiny_action}
85
+ <article class="dqa-result-card is-interpretation">
86
+ <div class="dqa-card-icon">☘</div>
87
+ <div>
88
+ <h3>{labels['interpretation']}</h3>
89
+ <p>{escape(card.interpretation)}</p>
90
+ <div class="dqa-anchor-strip">{labels['anchors']} {anchors}</div>
91
+ </div>
92
+ </article>
93
  {qa_history}
94
  {safety}
95
  {caring_note}
scripts/evaluate_today_tip_quality.py CHANGED
@@ -52,6 +52,13 @@ def evaluate_case(case: Dict[str, Any]) -> List[str]:
52
  view = _text_for_case(case)
53
  combined = "\n".join([view.get("card_title", ""), view.get("card_text", ""), view.get("card_html", "")])
54
  lowered = combined.lower()
 
 
 
 
 
 
 
55
  failures: List[str] = []
56
 
57
  if view.get("status") != "tip":
@@ -60,6 +67,16 @@ def evaluate_case(case: Dict[str, Any]) -> List[str]:
60
  if not any(anchor.lower() in lowered for anchor in case.get("required_anchors", [])):
61
  failures.append("missing_required_anchor")
62
 
 
 
 
 
 
 
 
 
 
 
63
  old_terms = [term for term in OLD_CUSTOMS_TERMS if term in lowered]
64
  if old_terms:
65
  failures.append("old_customs_terms:" + ",".join(old_terms))
 
52
  view = _text_for_case(case)
53
  combined = "\n".join([view.get("card_title", ""), view.get("card_text", ""), view.get("card_html", "")])
54
  lowered = combined.lower()
55
+ interpretation = (
56
+ view.get("debug", {})
57
+ .get("session", {})
58
+ .get("draft_tip", {})
59
+ .get("interpretation", "")
60
+ .lower()
61
+ )
62
  failures: List[str] = []
63
 
64
  if view.get("status") != "tip":
 
67
  if not any(anchor.lower() in lowered for anchor in case.get("required_anchors", [])):
68
  failures.append("missing_required_anchor")
69
 
70
+ missing_answer_terms = [term for term in case.get("required_answer_terms", []) if term.lower() not in lowered]
71
+ if missing_answer_terms:
72
+ failures.append("missing_answer_terms:" + ",".join(missing_answer_terms))
73
+
74
+ missing_interpretation_terms = [
75
+ term for term in case.get("required_interpretation_terms", []) if term.lower() not in interpretation
76
+ ]
77
+ if missing_interpretation_terms:
78
+ failures.append("missing_interpretation_terms:" + ",".join(missing_interpretation_terms))
79
+
80
  old_terms = [term for term in OLD_CUSTOMS_TERMS if term in lowered]
81
  if old_terms:
82
  failures.append("old_customs_terms:" + ",".join(old_terms))
tests/fixtures/today_tip_eval_cases.json CHANGED
@@ -11,7 +11,9 @@
11
  "dream_text": "I dreamed I was in an elevator where the floor buttons melted like wax. The number 14 kept blinking, and I felt late but strangely calm.",
12
  "mood": "Uneasy",
13
  "answer": "I want to make starting my overdue email easier without feeling trapped by it.",
14
- "required_anchors": ["elevator", "button", "14"]
 
 
15
  },
16
  {
17
  "id": "exam_without_pencil",
 
11
  "dream_text": "I dreamed I was in an elevator where the floor buttons melted like wax. The number 14 kept blinking, and I felt late but strangely calm.",
12
  "mood": "Uneasy",
13
  "answer": "I want to make starting my overdue email easier without feeling trapped by it.",
14
+ "required_anchors": ["elevator", "button", "14"],
15
+ "required_answer_terms": ["overdue email", "first sentence"],
16
+ "required_interpretation_terms": ["overdue email", "floor 14"]
17
  },
18
  {
19
  "id": "exam_without_pencil",
tests/test_render.py CHANGED
@@ -1,6 +1,12 @@
1
  from dream_customs.pipeline import add_evidence, create_session
2
- from dream_customs.render import render_pact_card, render_pact_inspector, render_status_bar, render_timeline
3
- from dream_customs.schema import PactCard
 
 
 
 
 
 
4
 
5
 
6
  def test_render_pact_card_contains_core_fields():
@@ -72,3 +78,21 @@ def test_status_bar_is_status_not_fake_navigation():
72
  assert "Current: Empty desk" in html
73
  assert "dc-phase-rail" not in html
74
  assert "<nav" not in html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from dream_customs.pipeline import add_evidence, create_session
2
+ from dream_customs.render import (
3
+ render_pact_card,
4
+ render_pact_inspector,
5
+ render_status_bar,
6
+ render_timeline,
7
+ render_today_tip_card,
8
+ )
9
+ from dream_customs.schema import PactCard, TodayTipCard
10
 
11
 
12
  def test_render_pact_card_contains_core_fields():
 
78
  assert "Current: Empty desk" in html
79
  assert "dc-phase-rail" not in html
80
  assert "<nav" not in html
81
+
82
+
83
+ def test_today_tip_card_prioritizes_tip_before_interpretation():
84
+ html = render_today_tip_card(
85
+ TodayTipCard(
86
+ dream_summary="You dreamed about an elevator.",
87
+ main_question="What feels hard to start?",
88
+ dream_anchors=["floor 14", "elevator"],
89
+ followup_questions=["What real task feels stuck?"],
90
+ user_answers=["An overdue email feels hard to start."],
91
+ interpretation="Maybe this dream is pointing to a stuck beginning.",
92
+ today_tip="Open the overdue email and write only the first sentence.",
93
+ tiny_action="Set a five-minute timer and stop after one sentence.",
94
+ caring_note="One line is enough for now.",
95
+ )
96
+ )
97
+
98
+ assert html.index("Today's small suggestion") < html.index("Maybe this dream is pointing to")
tests/test_ui_actions.py CHANGED
@@ -160,3 +160,29 @@ def test_english_today_tip_has_no_chinese_anchor_leakage():
160
  assert "overdue email" in combined.lower()
161
  assert "first sentence" in combined.lower()
162
  assert "immediately" not in combined.lower()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  assert "overdue email" in combined.lower()
161
  assert "first sentence" in combined.lower()
162
  assert "immediately" not in combined.lower()
163
+
164
+
165
+ def test_english_interpretation_uses_user_answer_before_tip():
166
+ state, _view_json = submit_dream_action(
167
+ dream_text=(
168
+ "I dreamed I was in an elevator where the floor buttons melted like wax. "
169
+ "The number 14 kept blinking, and I felt late but strangely calm."
170
+ ),
171
+ mood="Uneasy",
172
+ text_backend="demo",
173
+ vision_backend="demo",
174
+ language="en",
175
+ )
176
+
177
+ _state, view_json = answer_to_card_action(
178
+ state,
179
+ "I want to make starting my overdue email easier without feeling trapped by it.",
180
+ text_backend="demo",
181
+ vision_backend="demo",
182
+ language="en",
183
+ )
184
+ view = json.loads(view_json)
185
+ interpretation_line = next(line for line in view["card_text"].splitlines() if line.startswith("Interpretation:"))
186
+
187
+ assert "overdue email" in interpretation_line.lower()
188
+ assert "floor 14" in interpretation_line.lower()