Files

129 lines
7.0 KiB
Python

"""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] = [
# Only lines that are true of the built app belong here. This one file feeds the in-app card, the
# GitHub body AND the Help agent's context, so a line written for a planned feature becomes the
# agent confidently describing something that does not exist.
ReleaseNote(
version="1.7.7",
headline="Agents remember, apps get lighter, and fullscreen stays put.",
highlights=[
"Agents can now save facts you tell them and recall them in any later chat. You curate the list in Settings.",
"Apps you built can use your connected tools after you approve each app once; nothing is granted by default.",
"Apps nobody is editing serve a prebuilt copy instead of a full dev server, cutting hundreds of megabytes per open app.",
"Agent questions and permission asks arrive as clean step-by-step cards instead of plain text forms.",
"The model picker groups your subscriptions first, then API keys, then routers, so plans and pay-per-use never mix.",
"Dictation starts faster: the microphone stays warm for a few minutes after each use.",
],
fixes=[
"Passkey and Touch ID sign-in works again; the account picker was answering \"none\" and cancelling every attempt.",
"A momentary read error can no longer wipe a dashboard's layout permanently; it is treated as an error instead of an empty board.",
"Apps no longer get stuck on \"Starting preview\" forever; one app that served a prebuilt bundle used to wedge every other app until a restart.",
"Signing in with Google lands every time. The sign-in could previously finish in your browser and never reach the app, leaving you signed out after a reload.",
"Experimental builds stop flipping back and forth with the stable version on every restart.",
"If the app briefly loses its local connection it now retries and heals itself, and tells you plainly when it cannot, instead of spinning forever with no explanation.",
"Buttons on the question cards agents show you always respond, including on a collapsed chat, and answered questions never leave a second dead copy behind.",
"Images, posts and links agents show you open when you click them, and a click from fullscreen takes you there instead of doing nothing.",
"Agents stop building a whole app when you only asked to track or plan something; they offer first.",
"Creating a new app works again in the installed build; it previously hung on a blank preview forever.",
"Nothing can be dragged while a window is fullscreen, and older profiles with stuck fullscreen state are healed on load.",
"An agent that stops without answering now says so honestly instead of pretending it finished.",
"When a provider is retrying behind the scenes you see a live status pill instead of a frozen card.",
"App and browser icons in the sidebar are always real images, never letters or numbers.",
"Usage now reports the time agents actually spent working, not time spent waiting.",
],
),
ReleaseNote(
version="1.7.5",
headline="Off means off, and the canvas stops tearing.",
highlights=[
"Deleting a scheduled workflow makes it stay deleted. One that was mid-run could previously save itself back and keep firing.",
"Switching a workflow off now stops everything: no queued catch-up runs, nothing waiting on the review card.",
"Scrolling inside a panel, list, or window stays in that panel instead of dragging the canvas with it.",
"Opening a busy dashboard no longer locks the window while its cards wake up.",
],
fixes=[
"The canvas background no longer tears into a hard-edged rectangle when lots of browsers are open.",
"Editing a workflow step can no longer hang the editor when the naming service is slow.",
"A crashed session no longer leaves a key watcher running, which made dictation start and immediately stop.",
"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.",
],
),
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:
# An experimental build (1.7.6-exp1) tells the same story as its base release.
base = version.split("-", 1)[0]
for note in P_RELEASES:
if note.version == version or note.version == base:
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()
# The Help agent names the RUNNING version even on an experimental build riding its base release's story.
shown = app_version if release_notes(app_version) is note and app_version else note.version
body = [f"Version {shown}: {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}