diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d07e59c..5eccb3c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -414,8 +414,15 @@ jobs: # guard is kept: the tag, the release and the asset below do not need # master, so the site sees the release as soon as this step ends. The # `+` lets a re-dispatch replace the branch; nothing else writes to it. + # The runner's global pre-push hook caps a pushed branch at 1000 + # lines because a branch becomes a PR a person reads. This snapshot + # is not authored work: v0.8.0 was 64857 lines against the public + # master (run 36429780866, 2026-09-28) and the hook refused it, so + # nothing reached the public repository. The override is the hook's + # own, per push, the same one bin/quiet-corners-refresh.sh and + # bin/gms-journal-cron.sh already use for their pipeline pushes. BRANCH="release/$TAG" - git push origin "+HEAD:refs/heads/$BRANCH" + DIFF_CAP_OVERRIDE=1 git push origin "+HEAD:refs/heads/$BRANCH" # A tag already on the public repository is never moved: a re-dispatch # reuses it, and its tree is the same `git archive` of the same tag. if [ -z "$(git ls-remote --tags origin "refs/tags/$TAG")" ]; then diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c3f159..404c863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,30 @@ fragments into this file at release time. ## [Unreleased] +## [0.9.0] - 2026-10-08 + +### Changed +- `requirements.lock` follows the grouped pip bump it constrains: alembic to + 1.20.0, psycopg and psycopg-binary to 3.3.6, sqlalchemy to 2.0.54 and uvicorn + to 0.53.0. Dependabot compiles `requirements-runtime.txt` and leaves the + constraint file behind, so the two named different versions and + `tests/test_image_dependency_parity.py` failed on the bump. +- The static showcase's notice is now a sticky return bar on every demo page: + a link back to headlessmode.com, the "Fixed fictional demo" label, a link to + the driftless product page, and an "About this demo" disclosure holding the + source commit and export date. Both links are absolute, so the bar works + wherever the bundle is hosted, and headings keep a scroll margin so fragment + links are not hidden under it. `--home-url` sets the studio link. + +### Fixed +- The receipt footer's 64-character sha256 now wraps, so the project hub and status pages no longer scroll sideways at 390px. +- The Scope & Baseline document now honours its as-of date: baselines approved and change requests raised after it no longer appear, and an approved baseline with no recorded approval date is flagged "not recorded" instead of shown as dated history. +- The read-only showcase export no longer shows instructions that only make sense beside a form or the write API ("Use the form below", `POST /...`, `PATCH /...`, "Try a crash"); templates mark them with `data-showcase="write"` and a contract test keeps new ones marked. +- The artifact reference no longer says task links, requirement traces, the resource tree, calendars, equipment/material resources or deliverable verification are "not tracked" when the product records them. +- The methods page labels "fully crosswalked" as Driftless's own mapping, not identity with or certification against the standard. +- The read-only showcase export no longer shows the dashboard's empty-state API calls or the organization configuration's "Next action" column; both are marked `data-showcase="write"`, and the marker now covers table cells. +- The Scrum sprint-planning agenda no longer invites exceptions to the Definition of Done (The Scrum Guide, 2020: an item that misses it returns to the Product Backlog). + ## [0.8.0] - 2026-09-27 ### Added diff --git a/bin/driftless-showcase.py b/bin/driftless-showcase.py index 0c0ccf6..6d0968b 100644 --- a/bin/driftless-showcase.py +++ b/bin/driftless-showcase.py @@ -50,6 +50,15 @@ ) SCRIPT = re.compile(r"]*>.*?", re.IGNORECASE | re.DOTALL) FORM = re.compile(r"]*>.*?", re.IGNORECASE | re.DOTALL) +# Prose that only makes sense beside a form or the write API ("Use the form below", +# "POST /risks ...") is dead once the controls are gone. Templates mark it with +# ``data-showcase="write"`` on the smallest element holding it; marked elements never +# nest (tests/test_showcase_demo.py asserts that), so the lazy match to the same +# closing tag is exact. +WRITE_ONLY = re.compile( + r'<(span|p|li|ol|h2|th|td)\b[^>]*\bdata-showcase="write"[^>]*>.*?', + re.IGNORECASE | re.DOTALL, +) # The method map's filter strip is driven by static/map.js alone; with every script # stripped it would be a row of dead checkboxes and a Reset button that resets nothing. MAP_FILTERS = re.compile(r'
.*?
', re.DOTALL) @@ -413,9 +422,16 @@ def _rewrite_anchors(body: str, files: dict[str, frozenset[str]], snapshot: Modu #: so the URL is not hard-coded twice between the CLI default and the notice. DEFAULT_SITE_URL = "https://headlessmode.com/driftless/" +#: The studio home the return bar's first link points at (``--home-url``). +DEFAULT_HOME_URL = "https://headlessmode.com/" + def transform_bundle( - pages: dict[str, str], anchor: str, source: str, site_url: str = DEFAULT_SITE_URL + pages: dict[str, str], + anchor: str, + source: str, + site_url: str = DEFAULT_SITE_URL, + home_url: str = DEFAULT_HOME_URL, ) -> dict[str, str]: """Remove active behavior, label the fixture, and make page links portable.""" snapshot = _snapshot_module() @@ -425,13 +441,26 @@ def transform_bundle( files = {name: frozenset(ELEMENT_ID.findall(body)) for name, body in kept.items()} label = ( '" + f'← Headless Mode ' + 'driftless · Read-only demo · ' + f"Fixed fictional demo · as of {html.escape(anchor)} " + f'Product details ' + '
About this demo' + "

Every name, date and figure here is fictional, and nothing on these pages can be " + f"changed. Exported from driftless source {html.escape(source)}, " + f"as of {html.escape(anchor)}.

" ) style = ( - "