feat(dream): add one-minute recap report - #46
Conversation
📝 WalkthroughWalkthroughThe Dream feature now collects bucketed activity metrics, generates structured daily recaps, supports daily and weekly report history, defers habit persistence until confirmation, and provides redesigned responsive report views with expanded tests and localization. ChangesDream report pipeline
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant DaoDreamApp
participant GetDreamReports
participant WeeklyReportAPI
participant ReportRenderer
DaoDreamApp->>GetDreamReports: load daily report history
DaoDreamApp->>WeeklyReportAPI: load weekly report history
GetDreamReports-->>DaoDreamApp: return daily reports
WeeklyReportAPI-->>DaoDreamApp: return weekly reports
DaoDreamApp->>ReportRenderer: render the selected report
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/dao/browser/agent/dao_dream_browsertest.cc (1)
4551-4568: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win确保失败路径退出
verify_loop。如果
r为空,Line 4554 的ASSERT_TRUE会提前返回。verify_loop.Quit()不会执行。该浏览器测试会挂起直到超时。先调用verify_loop.Quit(),再验证结果。建议修复
runner.last_request().period_start, base::BindLambdaForTesting([&](std::optional<DreamReport> r) { + verify_loop.Quit(); ASSERT_TRUE(r.has_value()); EXPECT_EQ("completed", r->status); EXPECT_EQ("manual", r->trigger_kind); EXPECT_EQ("# nightly report", r->report_markdown); @@ const base::DictValue* stored_recap = material_stats->GetDict().FindDict("recap"); ASSERT_TRUE(stored_recap); EXPECT_EQ("A focused day.", *stored_recap->FindString("summary")); - verify_loop.Quit(); }));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/dao/browser/agent/dao_dream_browsertest.cc` around lines 4551 - 4568, 在 GetDreamReportByDate 的回调中,先调用 verify_loop.Quit(),再执行 ASSERT_TRUE(r.has_value()) 及后续断言,确保 r 为空等失败路径也能退出等待循环并避免测试挂起。
🧹 Nitpick comments (1)
src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts (1)
585-613: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the superseded legacy style block.
The stylesheet now declares two full themes.
:host,.page,main,.history-layout,.history-list,.history-item,.markdown,.report-body, and.dateeach appear twice, and there are two@media (max-width: 640px)blocks (Line 540 and Line 1314).The redesign wins by cascade order, so rendering is mostly correct. Two side effects remain:
- At widths ≤640px the legacy rule
.history-layout { display: block; }(Line 568-570) still applies. The new.history-layout { gap: 20px }is then ignored, so the rail and body lose their intended spacing.- The legacy
.history-list { margin-bottom: 22px; border-right: 0; }(Line 572-577) still applies on top of the new.history-listrules.Delete the legacy declarations that the redesign replaces. Keep only the rules that the new layout needs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts` around lines 585 - 613, Remove the superseded legacy style block in the stylesheet, including duplicate :host, .page, main, .history-layout, .history-list, .history-item, .markdown, .report-body, .date, and the earlier max-width: 640px media rules. Preserve only the redesign’s declarations so .history-layout retains its gap and .history-list no longer inherits legacy margin-bottom or border-right overrides.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@design-qa.md`:
- Around line 3-6: Remove the personal absolute filesystem path from the “Source
visual truth path” section in design-qa.md and replace it with a
repository-relative path to the version-controlled design artifact, or a
non-personal instruction for accessing the artifact without exposing a username.
In `@src/dao/browser/ui/webui/resources/agent/__tests__/dao_dream_app.test.ts`:
- Line 272: Make the formatted-date assertion in dao_dream_app.test.ts
locale-independent by configuring Vitest to use a fixed English locale, or by
asserting against a locale-independent representation instead of the hard-coded
English text. Preserve coverage of formatDreamDate_ and ensure the test passes
consistently across runner locales.
In `@src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts`:
- Around line 2053-2080: Remove the role="grid" and role="gridcell" attributes
from the activity heatmap elements in the cell-generation and heatmap-grid
markup, while retaining the buttons, their existing labels, and the heatmap
region’s aria-label.
- Around line 1420-1442: Update loadHistory_ so getWeeklyDreamReports failure or
timeout degrades to an empty weekly-report list instead of rejecting the
Promise.all operation; preserve successful daily report loading and avoid
clearing reports_ or setting the main error state when only the weekly request
fails.
- Around line 2586-2588: Update renderMarkdown_ so the Markdown output from
marked.parse is sanitized with DOMPurify.sanitize before being returned or
assigned via innerHTML. Ensure the weeklyReportMarkdown_ model-generated fields
remain rendered while both the parsed-Markdown path and fallback output are
safe.
In `@src/dao/browser/ui/webui/resources/agent/i18n/locales/en.ts`:
- Line 376: Update the dream.page.history_recent translation from “days” to a
neutral noun such as “reports” in both en.ts and zh-CN.ts, and update the
matching expected string in the dao_dream_app.test.ts fixture; keep
renderHistoryList_ behavior unchanged.
In `@src/dao/browser/ui/webui/resources/agent/i18n/locales/zh-CN.ts`:
- Around line 280-293: 统一昨日梦境报告中的时间指代:在 zh-CN 本地化条目
dream.page.rhythm_title、dream.page.theme_fallback 和 dream.page.stats_label
中,将“今日”改为“昨日”或移除时间指代,保持与 dream.page.recap_eyebrow 和 dream.page.recap_title 一致。
---
Outside diff comments:
In `@src/dao/browser/agent/dao_dream_browsertest.cc`:
- Around line 4551-4568: 在 GetDreamReportByDate 的回调中,先调用 verify_loop.Quit(),再执行
ASSERT_TRUE(r.has_value()) 及后续断言,确保 r 为空等失败路径也能退出等待循环并避免测试挂起。
---
Nitpick comments:
In `@src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts`:
- Around line 585-613: Remove the superseded legacy style block in the
stylesheet, including duplicate :host, .page, main, .history-layout,
.history-list, .history-item, .markdown, .report-body, .date, and the earlier
max-width: 640px media rules. Preserve only the redesign’s declarations so
.history-layout retains its gap and .history-list no longer inherits legacy
margin-bottom or border-right overrides.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5b35befc-75b1-4499-af66-ab8996b0e165
📒 Files selected for processing (19)
design-qa.mddocs/feature-checklist.mddocs/features.mdsrc/dao/browser/agent/dao_agent_memory_service.ccsrc/dao/browser/agent/dao_agent_memory_service.hsrc/dao/browser/agent/dao_agent_memory_store.ccsrc/dao/browser/agent/dao_agent_memory_store.hsrc/dao/browser/agent/dao_agent_memory_store_unittest.ccsrc/dao/browser/agent/dao_dream_browsertest.ccsrc/dao/browser/agent/dao_dream_material_collector.ccsrc/dao/browser/agent/dao_dream_material_collector.hsrc/dao/browser/agent/dao_dream_service.ccsrc/dao/browser/agent/dao_dream_service.hsrc/dao/browser/ui/webui/resources/agent/__tests__/dao_dream_app.test.tssrc/dao/browser/ui/webui/resources/agent/__tests__/dao_dream_runner.test.tssrc/dao/browser/ui/webui/resources/agent/dao_dream_app.tssrc/dao/browser/ui/webui/resources/agent/dao_dream_runner.tssrc/dao/browser/ui/webui/resources/agent/i18n/locales/en.tssrc/dao/browser/ui/webui/resources/agent/i18n/locales/zh-CN.ts
💤 Files with no reviewable changes (1)
- src/dao/browser/agent/dao_dream_service.h
| **Source visual truth path** | ||
|
|
||
| - Open Design HTML authority: `newtab.html`, `browsing.html`, `settings.html`, `history.html`, `bookmarks.html`, `downloads.html`, and `extensions.html`. | ||
| - Tokens and behavior: `DESIGN-TOKENS.md`, `HANDOFF.md`, and `ACCEPTANCE.md`. | ||
| - Exported visual reference: `image.png`. | ||
| - Desktop brand authority: the borderless `branding/dao_logo.png` source artwork. | ||
| - Runtime captures: API 34 Pixel 6 emulator at 1080 x 2400 in light and dark themes. | ||
| `/Users/moonrailgun/Library/Application Support/Open Design/namespaces/release-stable/data/projects/d7969910-805f-49b3-9d34-80d6d21d2420/dream-recap-redesign.html` | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
移除本机绝对路径和用户名。
该路径公开了 moonrailgun 本机用户名。其他开发者也无法访问该路径。请改用仓库内受版本控制的相对路径,或使用不含个人标识符的设计制品访问说明。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@design-qa.md` around lines 3 - 6, Remove the personal absolute filesystem
path from the “Source visual truth path” section in design-qa.md and replace it
with a repository-relative path to the version-controlled design artifact, or a
non-personal instruction for accessing the artifact without exposing a username.
| 'getDreamReports', {limit: 371}); | ||
| expect(el.shadowRoot!.textContent).toContain('2026-06-12'); | ||
| expect(el.shadowRoot!.textContent).toContain('2026-06-11'); | ||
| expect(el.shadowRoot!.textContent).toContain('Thu, Jun 11'); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Pin the locale for the formatted-date assertion.
formatDreamDate_ calls new Intl.DateTimeFormat(undefined, ...). The output depends on the runner locale. The literal 'Thu, Jun 11' passes only under an English locale. A CI image with a different default locale fails this test.
Set the locale explicitly in the Vitest config, or assert on a locale-independent value.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dao/browser/ui/webui/resources/agent/__tests__/dao_dream_app.test.ts` at
line 272, Make the formatted-date assertion in dao_dream_app.test.ts
locale-independent by configuring Vitest to use a fixed English locale, or by
asserting against a locale-independent representation instead of the hard-coded
English text. Preserve coverage of formatDreamDate_ and ensure the test passes
consistently across runner locales.
| private async loadHistory_() { | ||
| try { | ||
| const raw = await callNative('getDreamReports', {limit: 30}); | ||
| const [raw, rawWeekly] = await Promise.all([ | ||
| callNative('getDreamReports', {limit: 371}), | ||
| callNative('getWeeklyDreamReports', {limit: 53}), | ||
| ]); | ||
| const reports = this.normalizeReports_(raw); | ||
| const weeklyReports = this.normalizeWeeklyReports_(rawWeekly); | ||
| this.reports_ = reports; | ||
| this.report_ = reports[0] || null; | ||
| this.habitStates_ = | ||
| this.report_ ? this.loadPersistedHabitStates_(this.report_) : {}; | ||
| this.weeklyReports_ = weeklyReports; | ||
| this.report_ = this.historyReports_()[0] || null; | ||
| this.habitStates_ = this.report_?.reportKind === 'daily' ? | ||
| this.loadPersistedHabitStates_(this.report_) : {}; | ||
| if (this.report_) { | ||
| this.markReportViewed_(this.report_); | ||
| } | ||
| } catch (e) { | ||
| this.error_ = e instanceof Error ? e.message : String(e); | ||
| this.report_ = null; | ||
| this.reports_ = []; | ||
| this.weeklyReports_ = []; | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the weekly Dream native message handlers exist.
rg -n -C3 'getWeeklyDreamReports|markWeeklyDreamReportViewed' --type=cc --type=cpp --type=tsRepository: msgbyte/dao-browser
Length of output: 188
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files matching dao_dream_app.ts / relevant native message handlers:"
git ls-files | rg 'dao_dream_app\.ts$|dream|Dream|Report|report' | head -200
echo
echo "Search references:"
rg -n -C2 'getWeeklyDreamReports|markWeeklyDreamReportViewed|getDreamReports|markDreamReportViewed|callNative' || trueRepository: msgbyte/dao-browser
Length of output: 1610
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Find candidate handler files by name:"
git ls-files | rg '(^|/)(.*dream.*|.*report.*|webui.*|webui$).*\.(ts|cc|cpp|mm|h)$' | head -200Repository: msgbyte/dao-browser
Length of output: 12697
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "DAO agent UI native message registration:"
rg -n -C3 'getDreamReports|getWeeklyDreamReports|markWeeklyDreamReportViewed|DreamReports|WeeklyDream' src/dao/browser src/dao -g '*.{ts,cc,cpp,h,hpp,mm}' || true
echo
echo "Dream bridge TypeScript native message registration:"
rg -n -C3 'getDreamReports|getWeeklyDreamReports|markWeeklyDreamReportViewed|onMessage|message|native' src/dao/browser/ui/webui/resources/agent/dream_bridge.ts src/dao/browser/ui/webui/resources/agent/daodream*.ts src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts || true
echo
echo "Relevant dao_dream_app loadHistory section:"
sed -n '1380,1455p' src/dao/browser/ui/webui/resources/agent/dao_dream_app.tsRepository: msgbyte/dao-browser
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Exact getWeeklyDreamReports / markWeeklyDreamReportViewed native method definitions:"
rg -n 'getWeeklyDreamReports|markWeeklyDreamReportViewed|GetWeeklyDreamReports|MarkWeeklyDreamReportViewed|weekly.*Report.*handler|dream::' src/dao/browser -g '*.{cc,cpp,h,hpp,mm}' --max-count 50 || true
echo
echo "CallDream tests around weekly handlers (lines 5009-5105):"
sed -n '5009,5105p' src/dao/browser/agent/dao_dream_browsertest.cc
echo
echo "Relevant loadHistory_ section:"
sed -n '1380,1455p' src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts
echo
echo "Native promise/error type evidence for callNative:"
sed -n '1,180p' src/dao/browser/ui/webui/resources/agent/agent_bridge.ts
rg -n -C3 'callNative|callDream' src/dao/browser/ui/webui/resources/agent/agent_bridge.ts src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts --max-count 40 || trueRepository: msgbyte/dao-browser
Length of output: 24167
让周报加载失败不屏蔽每日历史数据。
Promise.all 因 getWeeklyDreamReports 失败或超时而拒绝时,当前捕获逻辑会把已加载的 reports_ 清空为 []。将周报请求降级为失败返回空列表,这样主显示路径不会因辅助数据出错而彻底空白。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts` around lines 1420
- 1442, Update loadHistory_ so getWeeklyDreamReports failure or timeout degrades
to an empty weekly-report list instead of rejecting the Promise.all operation;
preserve successful daily report loading and avoid clearing reports_ or setting
the main error state when only the weekly request fails.
| cells.push(html` | ||
| <button class="heat-cell" data-level=${String(level)} | ||
| style=${`grid-column:${column};grid-row:${index % 7 + 1}`} | ||
| role="gridcell" | ||
| title=${label} | ||
| aria-label=${label} | ||
| ?disabled=${!report} | ||
| @click=${report ? () => this.selectHistoryReport_(report) : | ||
| undefined}> | ||
| </button>`); | ||
| } | ||
| return html` | ||
| <section class="activity-heatmap"> | ||
| <div class="heatmap-header"> | ||
| <h2>${t('dream.page.activity_title')}</h2> | ||
| <span>${t('dream.page.activity_reports', { | ||
| count: this.reports_.length, | ||
| })}</span> | ||
| </div> | ||
| <div class="heatmap-scroll"> | ||
| <div class="heatmap-months"> | ||
| ${monthLabels.map(month => html` | ||
| <span style=${`grid-column:${month.column}`}>${month.label}</span>`)} | ||
| </div> | ||
| <div class="heatmap-grid" role="grid" | ||
| aria-label=${t('dream.page.activity_label')}> | ||
| ${cells} | ||
| </div> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the ARIA grid structure.
role="grid" requires role="row" elements between the grid and its gridcell children. Here the cells are direct children of the grid container, so the exposed structure is invalid and screen readers report an inconsistent widget.
The visual layout uses CSS grid-auto-flow: column, so there are no DOM rows to mark up. Remove the roles and keep the buttons plus the region label.
♿ Proposed change
<button class="heat-cell" data-level=${String(level)}
style=${`grid-column:${column};grid-row:${index % 7 + 1}`}
- role="gridcell"
title=${label}- <div class="heatmap-grid" role="grid"
+ <div class="heatmap-grid" role="group"
aria-label=${t('dream.page.activity_label')}>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cells.push(html` | |
| <button class="heat-cell" data-level=${String(level)} | |
| style=${`grid-column:${column};grid-row:${index % 7 + 1}`} | |
| role="gridcell" | |
| title=${label} | |
| aria-label=${label} | |
| ?disabled=${!report} | |
| @click=${report ? () => this.selectHistoryReport_(report) : | |
| undefined}> | |
| </button>`); | |
| } | |
| return html` | |
| <section class="activity-heatmap"> | |
| <div class="heatmap-header"> | |
| <h2>${t('dream.page.activity_title')}</h2> | |
| <span>${t('dream.page.activity_reports', { | |
| count: this.reports_.length, | |
| })}</span> | |
| </div> | |
| <div class="heatmap-scroll"> | |
| <div class="heatmap-months"> | |
| ${monthLabels.map(month => html` | |
| <span style=${`grid-column:${month.column}`}>${month.label}</span>`)} | |
| </div> | |
| <div class="heatmap-grid" role="grid" | |
| aria-label=${t('dream.page.activity_label')}> | |
| ${cells} | |
| </div> | |
| cells.push(html` | |
| <button class="heat-cell" data-level=${String(level)} | |
| style=${`grid-column:${column};grid-row:${index % 7 + 1}`} | |
| title=${label} | |
| aria-label=${label} | |
| ?disabled=${!report} | |
| `@click`=${report ? () => this.selectHistoryReport_(report) : | |
| undefined}> | |
| </button>`); | |
| } | |
| return html` | |
| <section class="activity-heatmap"> | |
| <div class="heatmap-header"> | |
| <h2>${t('dream.page.activity_title')}</h2> | |
| <span>${t('dream.page.activity_reports', { | |
| count: this.reports_.length, | |
| })}</span> | |
| </div> | |
| <div class="heatmap-scroll"> | |
| <div class="heatmap-months"> | |
| ${monthLabels.map(month => html` | |
| <span style=${`grid-column:${month.column}`}>${month.label}</span>`)} | |
| </div> | |
| <div class="heatmap-grid" role="group" | |
| aria-label=${t('dream.page.activity_label')}> | |
| ${cells} | |
| </div> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts` around lines 2053
- 2080, Remove the role="grid" and role="gridcell" attributes from the activity
heatmap elements in the cell-generation and heatmap-grid markup, while retaining
the buttons, their existing labels, and the heatmap region’s aria-label.
| <div class="report-body"> | ||
| ${this.renderMarkdown_(this.weeklyReportMarkdown_(report))} | ||
| </div> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect the markdown renderer for HTML escaping or sanitization.
fd -i 'markdown' --extension ts | xargs rg -n -C5 'renderDaoMarkdown|sanitiz|escapeHtml|DOMPurify|allowedTags'Repository: msgbyte/dao-browser
Length of output: 2367
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== dao_markdown.ts relevant lines =="
sed -n '1,130p' src/dao/browser/ui/webui/resources/agent/dao_markdown.ts | cat -n
echo
echo "== weekly report helper usages =="
rg -n "weeklyReportMarkdown_|renderMarkdown_\\(|renderDaoMarkdown\\(|escapeHtml\\(|sanitize|DOMPurify|xss|allowedTags" src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts src/dao/browser/ui/webui/resources/agent/dao_markdown.ts -C 3
echo
echo "== package references to marked/marked-sanitize/dompurify =="
rg -n "marked|marked-sanitize|mark.*sanitize|DOMPurify|purify|html-sanitizer|xss" package.json package-lock.json src/dao/browser/ui/webui/resources/agent src/dao -g '*.{ts,js,json}' || trueRepository: msgbyte/dao-browser
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '== matching candidate files (limited) ==\n'
fd -i 'dao_markdown|dao_dream' src/dao/browser/ui/webui/resources/agent --extension ts
printf '\n== dao_markdown.ts first 130 lines ==\n'
sed -n '1,130p' src/dao/browser/ui/webui/resources/agent/dao_markdown.ts | cat -n
printf '\n== weekly/daily markdown references in daemon ==\n'
rg -n "weeklyReportMarkdown_|renderMarkdown_|copyReportImage_|statusSummary|headline|nextStep|outcome\\.text" src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts -C 3
printf '\n== deterministic Marked behavior probe (import from bundled vendor code if available) ==\n'
python3 - <<'PY'
from pathlib import Path
import re
p = Path("src/dao/browser/ui/webui/resources/agent/dao_markdown.ts")
s = p.read_text()
print("contains escapeHtml:", bool(re.search(r"function escapeHtml|export function renderDaoMarkdown", s)))
m = re.search(r"function escapeHtml\(s: string\): string \{\s*return s\.replace\(/&/g,\s*'\&'\)(.*?)\n\}", s, re.S)
print("escapeHtml implementation:")
print(m.group(0) if m else "not found")
render = re.search(r"export function renderDaoMarkdown\(markdown: string\): string \{(?P<body>.*?)\n\}", s, re.S)
print("renderDaoMarkdown body:")
print(render.group("body") if render else "not found")
print("has escapeHtml calls:", bool(re.search(r"escapeHtml\(|DOMPurify|sanitize\\(", s)))
PYRepository: msgbyte/dao-browser
Length of output: 12865
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' "== locate vendor bundled marked source =="
rg -n "export default marked|marked =|function marked|renderer:" src/dao/browser/ui/webui/resources/agent/vendor/pi_runtime_bundle.js -C 3 || true
printf '%s\n' "== inspect bundled marked around renderer/decodeRawHtml/sanitize =="
rg -n "decodeRawHtml|sanitize|DOMPurify|escape|renderer|renderers|markedOptions|gfm|breaks|sanitize:" src/dao/browser/ui/webui/resources/agent/vendor/pi_runtime_bundle.js -C 4 || true
printf '%s\n' "== exact line ranges if found =="
python3 - <<'PY'
from pathlib import Path
p=Path("src/dao/browser/ui/webui/resources/agent/vendor/pi_runtime_bundle.js")
s=p.read_text().splitlines()
needles=["renderRawHtml","decodeRawHtml","sanitize","DOMPurify","renderer:","renderers:","escapeHtml()","<p>", "</p>"]
for i,line in enumerate(s,1):
if any(n in line for n in needles):
start=max(1,i-8); end=min(len(s),i+8)
print(f"-- lines {start}-{end}, match:{line.strip()} --")
for j in range(start,end+1):
print(f"{j:6d}\t{s[j-1]}")
print()
PY
printf '%s\n' "== runtime sanitizer usage in agent source =="
rg -n "DOMPurify|sanitize\\(|escapeHtml\\(|innerHTML|textContent" src/dao/browser/ui/webui/resources/agent src/dao -g '*.{ts,js}' -C 2 || trueRepository: msgbyte/dao-browser
Length of output: 1297
🌐 Web query:
Marked.js sanitize option raw HTML passed through by default
💡 Result:
The sanitize and sanitizer options in Marked.js were removed in version 8.0.0 [1][2][3]. Consequently, Marked.js does not sanitize the HTML it outputs by default, and it no longer provides a built-in mechanism to do so [4][5]. Because Marked.js does not provide sanitization, it is important to filter the output HTML to prevent XSS (Cross-Site Scripting) attacks if you are processing potentially unsafe input [4][5]. The official documentation recommends passing the output of Marked.js through a dedicated sanitization library such as DOMPurify (recommended), sanitize-html, or insane [1][2][5]. Example usage with DOMPurify: import { marked } from 'marked'; import DOMPurify from 'dompurify'; const dirtyHtml = marked.parse(markdownString); const cleanHtml = DOMPurify.sanitize(dirtyHtml);
Citations:
- 1: https://github.com/markedjs/marked/blob/master/docs/USING_ADVANCED.md
- 2: https://marked.js.org/using_advanced
- 3: https://github.com/markedjs/marked/blob/v17.0.5/docs/USING_ADVANCED.md
- 4: https://marked.js.org/
- 5: https://github.com/markedjs/marked/
🌐 Web query:
Marked.js encodeHtml escapeHtml default encode HTML options
💡 Result:
Marked.js does not provide specific configuration options named encodeHtml or escapeHtml to toggle HTML escaping behavior. In fact, Marked does not perform automatic sanitization of the output HTML [1]. Because Marked does not have built-in options to escape or encode HTML tags by default, you must manage security and HTML escaping using the following recommended approaches: 1. Use a dedicated sanitization library: The official Marked documentation explicitly warns that Marked does not sanitize output [1]. It strongly recommends using a library like DOMPurify, sanitize-html, or insane on the resulting HTML string before inserting it into the DOM [1]. 2. Customize rendering: If you need to force-escape HTML tags within the markdown content, you can create a custom renderer [2][3]. By overriding the html method in a custom renderer object, you can intercept HTML tokens and manually apply an escaping function to the text content before it is rendered [3]. Example of custom escaping using a renderer: const customRenderer = { html(token) { // Manually escape the token.text here return escapeHtml(token.text); } }; const markedInstance = new Marked({ renderer: customRenderer }); Marked has intentionally moved toward a philosophy of keeping the core parser focused on Markdown-to-HTML conversion and offloading specialized tasks (like XHTML self-closing tags or specific sanitization requirements) to extensions or external utilities [4].
Citations:
- 1: https://github.com/markedjs/marked/
- 2: https://github.com/markedjs/marked/blob/master/src/MarkedOptions.ts
- 3: How to escape HTML tags? markedjs/marked#3728
- 4: option to not use html entities markedjs/marked#1737
🌐 Web query:
Marked.js encodeHtml option true raw HTML escape default false no options
💡 Result:
There is no option named encodeHtml in the current version of Marked.js [1][2][3]. Additionally, Marked.js does not sanitize input HTML or escape raw HTML by default [4][5][6]. For security, the official documentation strongly advises against using Marked.js to parse untrusted input without an external sanitization library [4][5]. Developers are recommended to use a sanitizer such as DOMPurify, sanitize-html, or insane on the resulting HTML output [4][5][6]. While there is an internal helper function escape in the Marked codebase that accepts an encode parameter [7], this is not a public configuration option exposed to users for controlling raw HTML handling. Marked.js remains designed to output raw HTML tags found within the Markdown source by default [5].
Citations:
- 1: https://marked.js.org/using_advanced
- 2: https://github.com/markedjs/marked/blob/master/src/MarkedOptions.ts
- 3: https://deepwiki.com/markedjs/marked/4.2-configuration-options
- 4: https://github.com/markedjs/marked/
- 5: https://marked.js.org/
- 6: https://github.com/markedjs/marked/blob/0ccf4769/docs/INDEX.md
- 7: https://github.com/markedjs/marked/blob/0ccf4769/src/helpers.ts
🌐 Web query:
site:marked.js.org Raw HTML markdown allowed syntax
💡 Result:
Yes, raw HTML is allowed in most standard Markdown implementations, including the CommonMark specification and popular parsers like Marked [1]. When you include raw HTML tags directly in a Markdown file, most processors will pass them through to the final output unchanged [1]. This allows you to use HTML features not natively supported by Markdown—such as complex tables, styled spans, or specific attributes—directly within your document. Key considerations when using raw HTML in Markdown: 1. Security: Because Markdown processors generally do not sanitize raw HTML by default, embedding arbitrary HTML can introduce Cross-Site Scripting (XSS) risks if the content comes from untrusted users [1]. It is a standard industry best practice to use a sanitization library (such as DOMPurify) on the generated output before rendering it in a browser [1]. 2. Parsing Behavior: While most parsers support raw HTML, some may require specific configurations or adherence to block-level rules (e.g., ensuring tags are on their own lines or properly enclosed) to be processed correctly [1]. 3. Parser Differences: Although "raw HTML" is a common feature, the extent of support and how strictly it is parsed can vary slightly between different Markdown engines (like GitHub Flavored Markdown, Pandoc, or Marked) [1]. Always verify the behavior against the documentation of the specific processor you are using.
Citations:
Sanitize renderDaoMarkdown before assigning via innerHTML.
renderMarkdown_ inserts the markdown output with innerHTML, but renderDaoMarkdown only escapes raw input in the fallback <pre> branch; the m.parse(...) path returns Markdown output without dedicated sanitization. Since weeklyReportMarkdown_ includes model-generated fields (statusSummary, nextStep, outcome.text), pass marked.parse(...) through a sanitizer such as DOMPurify.sanitize(...) before assignment.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dao/browser/ui/webui/resources/agent/dao_dream_app.ts` around lines 2586
- 2588, Update renderMarkdown_ so the Markdown output from marked.parse is
sanitized with DOMPurify.sanitize before being returned or assigned via
innerHTML. Ensure the weeklyReportMarkdown_ model-generated fields remain
rendered while both the parsed-Markdown path and fallback output are safe.
| 'Run Dream Analysis from Agent settings to generate a report.', | ||
| 'dream.page.error': 'Failed to load dream report: {error}', | ||
| 'dream.page.history_title': 'History', | ||
| 'dream.page.history_recent': 'Recent {count} days', |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the history_recent wording for a mixed list.
The string reads Recent {count} days. renderHistoryList_ passes the size of the combined daily-and-weekly list. Weekly entries are not days, so the count and the noun disagree.
Use a neutral noun, for example Recent {count} reports. Apply the same change to the zh-CN value at src/dao/browser/ui/webui/resources/agent/i18n/locales/zh-CN.ts Line 276 and to the test fixture at src/dao/browser/ui/webui/resources/agent/__tests__/dao_dream_app.test.ts Line 87.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dao/browser/ui/webui/resources/agent/i18n/locales/en.ts` at line 376,
Update the dream.page.history_recent translation from “days” to a neutral noun
such as “reports” in both en.ts and zh-CN.ts, and update the matching expected
string in the dao_dream_app.test.ts fixture; keep renderHistoryList_ behavior
unchanged.
Source: Coding guidelines
| 'dream.page.rhythm_title': '今日节奏', | ||
| 'dream.page.rhythm_hint': '按前台专注时长', | ||
| 'dream.page.rhythm_morning': '上午', | ||
| 'dream.page.rhythm_afternoon': '下午', | ||
| 'dream.page.rhythm_evening': '晚上', | ||
| 'dream.page.rhythm_night': '深夜', | ||
| 'dream.page.minutes': '{count} 分钟', | ||
| 'dream.page.themes_title': '你在做什么', | ||
| 'dream.page.themes_count': '{count} 个主题', | ||
| 'dream.page.theme_light': '轻量', | ||
| 'dream.page.theme_medium': '专注', | ||
| 'dream.page.theme_deep': '深度', | ||
| 'dream.page.theme_fallback': '今日重点', | ||
| 'dream.page.stats_label': '今日活动统计', |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
统一时间指代:报告描述的是昨天。
dream.page.recap_eyebrow 为「昨日梦境回顾」,dream.page.recap_title 为「你昨天过得怎么样」。但同一页面的以下三个键使用「今日」:
- Line 280
dream.page.rhythm_title:「今日节奏」 - Line 292
dream.page.theme_fallback:「今日重点」 - Line 293
dream.page.stats_label:「今日活动统计」
这些区块渲染的是同一份昨日报告的数据。请改为「昨日」,或改用不带时间指代的措辞。
🌐 建议修改
- 'dream.page.rhythm_title': '今日节奏',
+ 'dream.page.rhythm_title': '当日节奏',- 'dream.page.theme_fallback': '今日重点',
- 'dream.page.stats_label': '今日活动统计',
+ 'dream.page.theme_fallback': '当日重点',
+ 'dream.page.stats_label': '当日活动统计',📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'dream.page.rhythm_title': '今日节奏', | |
| 'dream.page.rhythm_hint': '按前台专注时长', | |
| 'dream.page.rhythm_morning': '上午', | |
| 'dream.page.rhythm_afternoon': '下午', | |
| 'dream.page.rhythm_evening': '晚上', | |
| 'dream.page.rhythm_night': '深夜', | |
| 'dream.page.minutes': '{count} 分钟', | |
| 'dream.page.themes_title': '你在做什么', | |
| 'dream.page.themes_count': '{count} 个主题', | |
| 'dream.page.theme_light': '轻量', | |
| 'dream.page.theme_medium': '专注', | |
| 'dream.page.theme_deep': '深度', | |
| 'dream.page.theme_fallback': '今日重点', | |
| 'dream.page.stats_label': '今日活动统计', | |
| 'dream.page.rhythm_title': '当日节奏', | |
| 'dream.page.rhythm_hint': '按前台专注时长', | |
| 'dream.page.rhythm_morning': '上午', | |
| 'dream.page.rhythm_afternoon': '下午', | |
| 'dream.page.rhythm_evening': '晚上', | |
| 'dream.page.rhythm_night': '深夜', | |
| 'dream.page.minutes': '{count} 分钟', | |
| 'dream.page.themes_title': '你在做什么', | |
| 'dream.page.themes_count': '{count} 个主题', | |
| 'dream.page.theme_light': '轻量', | |
| 'dream.page.theme_medium': '专注', | |
| 'dream.page.theme_deep': '深度', | |
| 'dream.page.theme_fallback': '当日重点', | |
| 'dream.page.stats_label': '当日活动统计', |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dao/browser/ui/webui/resources/agent/i18n/locales/zh-CN.ts` around lines
280 - 293, 统一昨日梦境报告中的时间指代:在 zh-CN 本地化条目
dream.page.rhythm_title、dream.page.theme_fallback 和 dream.page.stats_label
中,将“今日”改为“昨日”或移除时间指代,保持与 dream.page.recap_eyebrow 和 dream.page.recap_title 一致。
Background
Dream reports needed a richer one-minute recap experience that surfaces daily activity, measured focus rhythm, themes, weekly context, and memory candidates without writing unconfirmed model suggestions directly to long-term memory.
Changes
dao://dreamaround a responsive recap layout with activity heatmap, shared daily/weekly history rail, summary, rhythm, themes, stats, memory candidates, and folded full report.Testing
Patch adds C++ store/browser tests and WebUI runner/app tests for session counting, annual report history, uncapped stats, structured recap validation, measured rhythm buckets, weekly history selection, legacy markdown fallback, and non-destructive memory rejection. Design QA notes report
npm run test:webuipassed with 61 test files and 728 tests; browser-rendered visual comparison remains blocked by unrelated Chromium import failures.Summary by CodeRabbit