[eric] release: one changelog source feeds the Help agent, the GitHub body, and the in-app card

This commit is contained in:
ciregenz
2026-08-07 00:45:06 -07:00
parent 3ba65bedb7
commit 9d8fe2e545
3 changed files with 141 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
"""The release story, in one place, for three surfaces: the in-app What's New card, the GitHub
release body, and the Help agent's context. One source means the agent can never answer from a
stale picture of the app, and a release can never ship with no story."""
from typing import Dict, List
from pydantic import BaseModel, ConfigDict
from typeguard import typechecked
class ReleaseNote(BaseModel):
model_config = ConfigDict(validate_assignment=True)
version: str
headline: str
# User-facing lines only: what changed for the person using the app, not the diff.
highlights: List[str]
fixes: List[str]
P_RELEASES: List[ReleaseNote] = [
ReleaseNote(
version="1.7.5",
headline="Fewer dead ends, and the app tells us when something breaks.",
highlights=[
"Scrolling inside any panel, chat, or browser stays in that panel instead of dragging the canvas.",
"Clicking the Marketplace window frames it like every other window, and camera moves land softly instead of snapping.",
"While a browser agent works you see the live mini browser only, not the same action log twice.",
],
fixes=[
"Dictation lands in the field you started in, even if you click elsewhere while it is still transcribing.",
"The first message after opening a chat reuses the warmed-up connection, so it answers sooner.",
"A provider hiccup that fixes itself no longer shows a scary reconnect card.",
"Crashes, freezes, and runaway memory now report themselves, so bugs get diagnosed instead of guessed at.",
],
),
ReleaseNote(
version="1.7.4",
headline="Chats survive a hiccup instead of stopping.",
highlights=[
"A dropped local connection retries and resumes the same answer instead of failing the message.",
"The spawn composer steps aside when a window is open.",
],
fixes=[
"App previews reconnect on their own after a backend restart.",
"Dictation cue sounds default to a level you can actually hear.",
],
),
]
@typechecked
def release_notes(version: str) -> ReleaseNote | None:
for note in P_RELEASES:
if note.version == version:
return note
return None
@typechecked
def latest_release() -> ReleaseNote:
return P_RELEASES[0]
@typechecked
def as_markdown(note: ReleaseNote) -> str:
"""The GitHub release body; identical words to the in-app card, so nobody reads two stories."""
lines = [f"## {note.version}{note.headline}", ""]
if note.highlights:
lines.append("### New")
lines += [f"- {h}" for h in note.highlights]
lines.append("")
if note.fixes:
lines.append("### Fixed")
lines += [f"- {f}" for f in note.fixes]
return "\n".join(lines).strip()
@typechecked
def help_context_block(app_version: str) -> str:
"""What the Help agent must know about what just changed, so "what's new" is never stale."""
note = release_notes(app_version) or latest_release()
body = [f"Version {note.version}: {note.headline}"]
body += [f"- new: {h}" for h in note.highlights]
body += [f"- fixed: {f}" for f in note.fixes]
return "\n".join(body)
@typechecked
def all_versions() -> Dict[str, str]:
return {n.version: n.headline for n in P_RELEASES}
+8
View File
@@ -136,6 +136,9 @@ def p_issues_block() -> str:
@typechecked
from backend.apps.help.changelog import help_context_block
def build_system_prompt(shortcuts: List[HelpShortcut], app_version: str) -> str:
shortcut_lines = "\n".join(f"- {s.keys}: {s.action}" for s in shortcuts)
os_name = "macOS" if IS_MAC else platform.system()
@@ -158,6 +161,11 @@ def build_system_prompt(shortcuts: List[HelpShortcut], app_version: str) -> str:
shortcut_lines,
"</shortcuts>",
"",
"<whats_new>",
"What actually changed in this build. Answer \"what's new\" from THIS, never from memory.",
help_context_block(app_version),
"</whats_new>",
"",
"<known_issues>",
"The complete list of issues shipped with this build. You cannot see live bug reports.",
p_issues_block(),
+43
View File
@@ -0,0 +1,43 @@
"""One release story, three surfaces. A release with no story, or a Help agent answering from a
stale picture of the app, are both bugs this pins shut."""
from backend.apps.help.changelog import (
all_versions, as_markdown, help_context_block, latest_release, release_notes,
)
from backend.apps.service.version import APP_VERSION
def test_the_shipping_version_has_a_story():
note = release_notes(APP_VERSION)
assert note is not None, f"{APP_VERSION} ships with no release notes; write them before tagging"
assert note.headline and (note.highlights or note.fixes)
def test_notes_are_written_for_users_not_committers():
for note in (latest_release(),):
for line in note.highlights + note.fixes:
assert not line.startswith("["), "no commit-style prefixes"
assert "commit" not in line.lower() and "refactor" not in line.lower()
assert "" not in line and "" not in line, "house style: no em/en dashes"
def test_markdown_body_carries_the_same_words_as_the_app():
note = latest_release()
md = as_markdown(note)
assert note.headline in md
for line in note.highlights + note.fixes:
assert line in md, "the GitHub body must not drift from the in-app card"
def test_help_context_names_the_version_and_its_changes():
block = help_context_block(APP_VERSION)
assert APP_VERSION in block
note = release_notes(APP_VERSION)
assert note is not None
assert note.highlights[0] in block
def test_unknown_version_falls_back_to_the_latest_story_not_silence():
assert release_notes("0.0.0") is None
assert latest_release().version in help_context_block("0.0.0")
assert len(all_versions()) >= 2