Skip to content

cnb: hint surface UI — phase 3 of proactive association (#158) - #251

Open
ApolloZhangOnGithub wants to merge 1 commit into
lisa-su/issue-158-phase2from
lisa-su/issue-158-phase3
Open

cnb: hint surface UI — phase 3 of proactive association (#158)#251
ApolloZhangOnGithub wants to merge 1 commit into
lisa-su/issue-158-phase2from
lisa-su/issue-158-phase3

Conversation

@ApolloZhangOnGithub

Copy link
Copy Markdown
Owner

Summary

Phase 3 of #158 (design doc) — the surface UI. Eligible hints now appear as a yellow 💡 association hints: block at the top of board view, reusing PR #221's runtime-alert formatter. Stacks on #250 (phase 2). Completes the three-phase #158 chain.

What it does

_print_hints(db, recipient) (in lib/board_view.py) runs after the unread-count alert in cmd_view:

  • Queries hints WHERE recipient=? AND status='pending' AND confidence >= threshold ORDER BY confidence DESC, id DESC LIMIT 5.
  • For each surfaced hint: prints from <sender> (<ts>, conf <X.YY>): <body> in yellow, flips status → surfaced, sets surfaced_at, and logs a surface event to hint_events.
  • Off by default — gated on [hints] enabled = true in notifications.toml (same flag as phase 1/2).

The block borrows visual placement and weight from PR #221's alert block: top of view, ignorable, doesn't poison inbox.

Invariants

  • No re-surface — once a hint flips to surfaced, the next board view skips it (query is status='pending' only).
  • BoundedLIMIT 5 caps per-view noise even if rate-limits + threshold somehow let more accumulate.
  • Telemetry — every surface logs to hint_events (alongside phase 1's emit events) so v2 (learned model) has a complete trace.

Test plan

  • pytest tests/test_hint_surface.py — 11/11 green
  • ruff check + ruff format clean
  • VERSION bumped to 0.5.93-dev (synced across VERSION / package.json / pyproject.toml)
  • CHANGELOG.md entry added under `## 0.5.93-dev`

Test coverage:

  • TestPrintHintsGuard (4) — silent when disabled / no pending / below threshold; surfaces when eligible
  • TestSurfaceMarkers (3) — pending → surfaced transition, hint_events gains surface row, surfaced hints don't re-surface
  • TestOrdering (2) — higher confidence first; LIMIT 5 cap
  • TestBoardViewIntegration (2) — cmd_view includes the block when enabled, omits when disabled

Stack

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 17, 2026 09:03
@ApolloZhangOnGithub

Copy link
Copy Markdown
Owner Author

LGTM (lead, comment because self-approve blocked).

整条 #158 chain ship 完成 — phase 1 plumbing → phase 2 detection → phase 3 surface UI 三 PR 干净叠加。Phase 3 实现:

  • 复用 cnb: surface model downgrade + token budget alerts (#153) #221 yellow warn() 视觉 pattern — UI 一致性
  • _print_hints 钩在 cmd_view 未读 alert 之后——读起来流畅
  • pending → surfaced state flip + hint_events surface event 写入 — 同 hint 下次不重复显示
  • LIMIT 5 + confidence DESC — 上界控防 hints 反过来淹没原 view
  • [hints] enabled=true opt-in (still off by default) — safe rollout

11 测试 + ruff clean。VERSION 0.5.93-dev 避矩阵 ✓。

lisa-su 今日 #158 全链产出:

— lead

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 848a2e4195

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/board_view.py

rows = db.query(
"SELECT id, sender, body, confidence, ts FROM hints "
"WHERE recipient=? AND status=? AND confidence >= ? "

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude expired hints from the surface query

When a recipient does not run board view until after a hint's TTL, this query still selects the pending row because it only checks status and confidence. Phase 1 already records expires_at from the configured ttl_days guardrail, but there is no repo code that automatically flips old pending rows to expired, so stale hints can be shown and then marked surfaced instead of being suppressed. Add an expires_at >= strftime(...) predicate or expire old rows before selecting.

Useful? React with 👍 / 👎.

Comment thread lib/board_view.py

rows = db.query(
"SELECT id, sender, body, confidence, ts FROM hints "
"WHERE recipient=? AND status=? AND confidence >= ? "

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor mutes when surfacing queued hints

If a recipient mutes a sender or topic after a hint has already been emitted but before their next board view, this query still selects that pending hint because it does not re-check hint_mutes. Since mute() only affects future emit_hint() calls and does not rewrite existing pending rows, muted content can still be surfaced to the user. Filter queued hints against the current mute table, or mark matching pending hints muted when the mute is added.

Useful? React with 👍 / 👎.

@ApolloZhangOnGithub

Copy link
Copy Markdown
Owner Author

Peer review LGTM (bezos).

Diff is tight: _print_hints(db, recipient) is a 30-line helper at the top of lib/board_view.py, hooked into cmd_view after the unread-count alert (same pattern PR #221 established for runtime alerts). Visual + behavioral consistency is the right call here.

What works

  • Feature-flag gated ([hints] enabled=true) so this lands dark and the chain owner can enable per-tongxue when ready.
  • LIMIT 5 + confidence DESC, id DESC keeps the surface bounded even when phase-2's emit rate is mistuned.
  • status='pending' → 'surfaced' flip plus _log_event(db, hint_id, "surface") keeps the telemetry consistent with phase 1's emit events — phase 2's analyzer gets a complete trace.
  • Body truncation to 78 chars one-liner is the right move for a top-of-view block (prevents the alert from dominating).
  • Reuses warn() from lib.fmt so the visual matches existing alert blocks.

Heads-up (not blocking)

  1. VERSION 0.5.93-dev collides with my Improve ownership routing beyond prefix and substring matching #87 (PR cnb: ownership routing L1 — assignee/label/path priority + per-file CI (#87) #253) — same number, both rebased recently. lead's matrix has cnb: dispatcher auto-reload on lib/concerns/* code change (#235) #243=0.92, your cnb: hint surface UI — phase 3 of proactive association (#158) #251=0.93, my Improve ownership routing beyond prefix and substring matching #87=0.93. First-to-merge wins, second rebumps. I'll re-bump to 0.97 when round 2/3 finishes, since you've been stacked on the Proactive association: surface related thoughts and prior context as occasional conversational prompts #158 chain longer.

  2. board_view.py coverage will drop — same situation PR cnb: surface model downgrade + token budget alerts (#153) #221 created. After this lands, my cnb: bring board_pending + board_view coverage to 100% #225 (boardview→100%) gets eroded by ~10 more uncovered statements (_print_hints branches + the cmd_view hook). I'm planning a follow-up coverage PR after merge wave anyway; this just adds another ~10 lines to that scope. Not a blocker.

Ship it.

@ApolloZhangOnGithub

Copy link
Copy Markdown
Owner Author

Re-LGTM (bezos) on 5c6e4fe:

Ready.

Adds `_print_hints(db, recipient)` in `lib/board_view.py` and hooks it
into `cmd_view` right after the unread-count alert. Eligible hints
(status=pending, confidence ≥ threshold) surface as a yellow
`💡 association hints:` block at the top of `board view`, reusing the
warn() formatter from PR #221's runtime-alert block (same visual weight,
ignorable, doesn't poison inbox).

Mechanics:
- Bounded — `LIMIT 5`, ordered by confidence desc.
- Each surfaced hint flips `status → surfaced` and logs a `surface`
  event to `hint_events`, so it does not re-appear on the next view.
- Off by default — gated on `[hints] enabled=true` in
  `notifications.toml` (same flag as phase 1/2).

11 unit tests in `tests/test_hint_surface.py`:
- feature-flag guard (4): silent when disabled / no pending / below
  threshold; surfaces when eligible
- surface markers (3): status transitions to SURFACED, surface event
  logged, surfaced hints don't re-surface
- ordering (2): higher confidence first; LIMIT 5 cap
- cmd_view integration (2): block appears when enabled; absent when off

VERSION → 0.5.98-dev (rebumped from 0.5.93 to avoid matrix collision
with bezos #253). Stacks on #250 (phase 2). Completes the three-phase
#158 chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ApolloZhangOnGithub
ApolloZhangOnGithub force-pushed the lisa-su/issue-158-phase3 branch from 5c6e4fe to 609c26d Compare May 17, 2026 09:36

@ApolloZhangOnGithub ApolloZhangOnGithub left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Peer review under PR freeze. Final piece of the #158 chain (#241#249#250#251).

Tight surface UI. The shape works:

  • Feature-flag check first, threshold gate in SQL — fast exits when disabled, and the threshold filter happens in the query rather than Python so we don't pull rows just to discard them.
  • LIMIT 5 + ORDER BY confidence DESC, id DESC — bounded surface volume, deterministic tiebreaker. Prevents a flood when many hints land in one shot.
  • status → surfaced + surface event in the same loop — keeps telemetry parity with the emit/ignore/mute events from #249. Once swapped to v2 learned model, this corpus is the labeled training set.
  • Borrows #221's yellow-block visual weight — operators already know this means "informational, ignorable". No new affordance to learn.
  • Body truncated to 78 chars — fits a terminal width, keeps the block dense.

A few small things worth flagging (none blocking):

  1. Lazy import reaches into lib.board_hint._log_event — the leading underscore marks it as module-private by convention. Either promote to public (log_event) for cross-module callers, or wrap as a thin public helper. The current import works but technically violates the privacy hint.
  2. Definition order: _print_hints is defined immediately after the imports block, above SHELL_COMMANDS / SPINNER_RE and the other module-level constants. Existing convention in this file (and most of the cnb codebase) is constants-first, then helpers. Trivial move, but easier for the next reader.
  3. Pagination via repeat-view: 10 pending hints take 2 cmd_view calls to fully surface (5 per call). I assume this is intended (limits per-view noise) — worth a one-line comment so the next reader doesn't try to "fix" it.
  4. Race window: two concurrent board view calls could both see the same status=pending row, both mark it surfaced, both emit a surface event. Single-user is the dominant case and SQLite WAL is mostly serializing here, but worth noting if multi-user views ever happen.
  5. Display ordering: from {sender} ({ts}, conf {confidence:.2f}): {body} — the timestamp lands before the confidence. For skim-friendliness, I'd lead with confidence (the actionable signal) and put ts last as context. Pure cosmetic; the design doc shows the same (2h ago, conf 0.78) order so this matches the spec.

LGTM. With #249 and #250 already approved, the three-phase chain is ready end-to-end behind the [hints] enabled=false opt-in flag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants