Conversation
A category section exists only while its filter renders it, so once another filter was picked the menu's #<category>-section links changed the address and moved nothing. examples.js now resolves a gallery anchor to its filter on a click, on Back/Forward and when the page opens at one, keeps the address in step with the filter pills, and moves focus to the gallery as a followed link would. Two featured ids named no card, three no-JavaScript fallback links named PDFs the site does not ship, the structured data said JVM 21+ where every module targets Java 17, and the page counted 16 CV presets where 26 ship. ShowcaseSiteGuardTest, added to the CI guard job, fails on a featured id, card file, site link, anchor or filter pill that resolves to nothing, and reads examples.json with a strict JSON reader so a manifest the page could not load fails too.
A card cropped its preview to a 248-pixel band from the top, so a document was judged by its header and a wide slide lost its sides. Cards and featured tiles now shrink the page into a fixed-height box at its own aspect ratio, and the image tags drop the 595x842 size that was wrong for the 26 previews that are not A4.
A card opened a lightbox holding a single image: to compare the CVs a reader returned to the grid for each one, and nothing in the address said which document was on screen, so a document could not be shared, reopened or reached with Back. A card, a featured tile or a family tile now opens its family in a viewer that shows the whole first page, moves with Previous, Next and the arrow keys, says where it is in the family, and links the PDF and the source of the document shown. A switch moves to the other families of the category, each reopening on the document it was left on. The address #/<category>/<family>/<id> reopens the same document on a reload and from a shared link: opening records one history entry, moving inside the viewer replaces it, and closing returns the address and the focus to where the reader came from. - gallery-viewer.js: the addresses, the navigation over the catalogue, and the dialog. Left and Right are read from the document while it is open, because at the ends of a family the step button holding focus is disabled and the browser hands focus back to the page, which would take the keys with it; a button about to be disabled passes focus to the other step, or to Close. - examples.js: the catalogue the viewer navigates, the family strip, and the routing on a click, on load and on hashchange. A viewer address selects the filter of its own category, so closing lands there, and a browser without <dialog> shows that gallery rather than the top of the page. - ShowcaseSiteGuardTest: card, family and category ids are unique and stay readable in an address, and a viewer address written into a page must name a family and a document it holds. - scripts/site/gallery-viewer.test.mjs: 17 cases over the addresses, the navigation and the dialog, the last four run against a DOM small enough to live in the file — the history entries an open and a move record, what closing hands back, the token that keeps a late preview off a newer document, and the focus a disabled button must not take out of the dialog. The guard job runs every harness in scripts/site/, so a second one cannot sit there never running.
…y holds The viewer moved only on its two arrows and the arrow keys. On a phone that left a 44px target as the only way forward, nothing on screen said what the other 26 CVs looked like, and every step waited on a page image that had not started loading until the reader asked for it. A drag across the page now moves a document, a strip under the page holds every document of the family, and the pages either side are fetched before they are asked for. The strip marks the document on screen and follows it, whether the reader got there by a drag, an arrow or the strip itself. - gallery-viewer.js: the drag is Pointer Events, touch and pen only — a mouse has the buttons and the keys. One pointer at a time, so a second finger is a pinch and not a swipe; nothing from a pinch-zoomed page; at least max(48px, 15% of the stage) across and more across than down; and none of it from within 24px of the screen edge, where the drag belongs to the browser's own back gesture. The click the browser sends after a drag is swallowed, so a swipe never also presses what it ended on. The strip and the family chips share one keepInView, which stops animating for a reader who asked for less motion. - The thumbnails point at the card previews the gallery behind the dialog already loads, and files sized for a 46px slot belong with the catalogue they are generated from. A reader who has asked to save data gets no strip at all: a row of whole pages is the one thing that mode is asking not to download, and the pages either side are not fetched ahead either. The strip is a single tab stop, on the document shown, so a keyboard reader reaches the page's own links without passing 27 thumbnails. - index.html, styles.css: the strip, and `touch-action: pan-y pinch-zoom` on the stage so a vertical drag still scrolls and a pinch still zooms. - scripts/site/gallery-viewer.test.mjs: eleven cases more, run against the same stub DOM, which grew a stage with a width, a strip with a row to scroll and a clock the test drives. The strip is the family's, marks one document and is a single tab stop; a thumbnail replaces the history entry rather than adding one, and the one already shown asks for nothing; the neighbours are fetched ahead, and neither they nor the strip in data-saver mode; the document shown is brought into view, and not smoothly for a reader who asked for less motion; a short, vertical, two-fingered, pinched, moused or edge-started drag is not a swipe, and one that only clears 48px is not a swipe on a wide page either; and the click a drag leaves behind presses nothing, inside a window that shuts on its own timer.
A card carried a title, a description, tags, its files and a link to its source. That is enough to draw it and nothing else: the site could not say which preset renders a document, what a reader needs on their classpath to run it, how many pages it has, or how large its preview is. The viewer's strip and the family tiles, both of them small, pointed at the same whole-page previews the cards use. - ShowcaseMetadata: a card now carries what kind of card it is, the repo-relative path to its source, the artifacts a reader needs, and — where its example builds exactly one template preset — that preset and the model it composes, plus the card it re-renders with different options. The source link and the path are built in one place, so they cannot drift apart. A card in a template category is an example needing the engine alone until the preset pass promotes it: three of them build no preset at all, and claiming one, or a module they never touch, is a line a reader pastes into their own pom. A feature card that does build one stays a feature card — invoice-http-stream builds ModernInvoice only to have a document worth streaming. - ShowcaseSync: the manifest gains a schemaVersion, and each card the preview's pixel size and the document's page count — both read from the render already being done — beside the register's new fields. Every control character is escaped as \u00XX, not just the five with short forms; a raw one is refused by the page's own fetch. Each document also gets a 320px thumbnail under showcase/thumbnails/, halved a step at a time, because one bilinear step from a page that wide samples too little of it and leaves fine strokes aliased. - ShowcasePresetRegistrationTest: holds each card's preset and model to the example that builds them, read from its source rather than from the register, so a renamed preset or a card carrying its neighbour's cannot reach the site. What a card says it is and what it asks for are checked in both directions. It found two feature cards asking a reader for the engine alone while rendering a template preset. - ShowcaseSiteGuardTest: fails on a manifest with no schemaVersion or one written to a contract the site does not read, a card whose measurements are not its preview's, a page count below one, a thumbnail that is not published or is no longer thumbnail-sized, and a preset count in the page copy the catalogue does not hold — 26 CV presets and 15 letters, counted as distinct presets, since a variant re-renders one rather than adding one. - examples.js, gallery-viewer.js: a card declares its preview's real width and height, so the image reserves its slot at the right shape instead of appearing out of nothing; the strip and the family tiles read the thumbnails. Opening the CV family fetches 1.4 MiB of strip images where it fetched 5.2 MiB. - The catalogue is regenerated. Five documents are published with their previews, all of them carrying right-to-left text, each proven to differ from the committed copy inside its page streams while rendering byte-for-byte the same twice over; the decks of the two that have one go with them. The other 112 documents and 6 decks are restored: every PDF differs run to run only inside /ID, and every deck only in the zip's modification time.
The published site stated the version in five places that inherit from no pom, and the cut rewrote the first match of each. That is two failures waiting: a page carrying a second install snippet keeps it a release behind while every check passes, and a spot that stops matching — renamed, reformatted, moved — is skipped silently, so the cut reports success and publishes a page still naming the previous release. The guard read four of those spots, also first-match, and never read the Maven Central download link at all. - index.html: a `release-context` JSON block holds stableVersion, releaseTag and javaMinimum. The JSON-LD and the install snippets still repeat the version, because a crawler and a reader with no JavaScript both have to see the right release; what changes is that they are now copies of something, and every copy is held equal to it. - cut-release.ps1: the bump covers seven spots and every occurrence of each, and a pattern that matches nothing throws, naming the spot, instead of leaving it behind. Its no-change notice also spent its life reporting nothing — `"$tag?"` reads as a variable called `tag?`, so a maintainer re-running a cut was told the page was "already ()". - release-script-check.yml: the function is lifted by AST and run on a fixture whose second Gradle snippet starts a release behind. Eight tokens move, a second run changes nothing, and a renamed spot refuses without writing. - VersionConsistencyGuardTest: every occurrence of all seven spots is held to the release, including the downloadUrl, and a shape that has vanished fails rather than passing for want of anything to check. The showcase snippet pattern arrays go with the first-match checks that used them. - web/README.md and docs/contributing/release-process.md say where the version lives, that every occurrence moves, and that a vanished spot stops the cut.
The gallery showed a document and a link to its source, and stopped there. A reader who wanted one of their own had to guess the coordinates, find the class to call, and work out which artifacts that particular document needs — and the catalogue could not have told them correctly: of the 117 cards, 53 named a dependency set that compiles and then throws on a missing font, ten left out a backend the card needs, and two offered a command that cannot run at all. - The viewer carries a panel under the document: the Maven and Gradle coordinates at the release the page names, the preset class and the record it composes, the family's worked snippet, the command that runs the example, and the runnable source and the family's guide at the release tag. A card that builds no preset is offered as a runnable example and makes no preset claim. With no release context it names no coordinates at all rather than a version it does not know. - The snippet is the block `DocumentationSnippetCompileTest` already compiles, copied into the manifest at sync time: the site is served from `web/` alone and cannot read a page under `docs/`, so the alternative was hand-written code on the one page that must not be hand-written. The page that teaches this path, `using-templates.md`, taught it in ellipses — `CvDocument doc = …` — and is now real code behind that marker. - A document drawn in a bundled face cannot be reproduced from `graph-compose` and `graph-compose-templates`: it compiles, then throws `Bundled font resource not found` at the first glyph, because those faces left the engine in v1.8.0. The artifact carrying them keeps its own version line — Central has 1.0.0 and 1.1.0 against an engine at 2.4.0 — so naming it at the release version is a 404, and the aggregate is the one coordinate correct at the version the page shows. Whether a document needs it is measured from the PDF, not declared: it differs card by card inside one family, 25 of 27 CVs and 4 of 7 invoices. - Fonts were not the only omission. Ten cards reach a second backend and asked a reader for the engine alone, which ends in `MissingBackendException` at render. Two name the DOCX backend in an import; nine need the PPTX one, which is discovered by format and so appears in no source at all — eight of those publish a deck beside their PDF, four of them rendered by a sibling class, so keying on the example's own text would still have missed them. The requirement now follows from what a card publishes as well as from what its example names, and where a document also needs the bundled faces the aggregate stands in for the engine and templates without swallowing the backend beside it. `table-advanced` and `transforms` have no `main` — `GenerateAllExamples` renders them — and were offered an `exec:java` command that answers "doesn't contain a main method"; they now say what renders them instead. - Every claim is checked against the example's own source or its rendered document, in both directions: `ShowcaseBundledFontClaimTest` for the faces, `ShowcaseCardInstructionsTest` for the backends and the run command, `ShowcaseSnippetScopeTest` for whether a published snippet even comes from a page the compile gate scans, and `ShowcaseSiteGuardTest` for a snippet that has drifted from its block, a `PRESET` card missing what the panel shows, and a family guide that is no longer a page. `s9-cv-templates` renders a CV from the published aggregate with nothing else installed, which is how the first of these defects surfaced.
…k its room Two things a reader could not do in front of a document. A multi-page one could only be judged by its first page or by downloading the PDF — 33 of the 117 documents have more, up to eight. And the panel added under the document took 42% of the viewport against the stage's 21%: an A4 page rendered at 104x147, which is a thumbnail with a dialog around it. - The panel is collapsed behind a disclosure and opens when a reader asks for it, staying open for the rest of the session so paging through a family does not re-collapse it on every document. Collapsed, the stage holds 54-71% of the viewport across 360/390/768/1440 against the 21% that shipped; opened it holds 24-26%, still more than the regression left it by default. Its cap came down from 42vh to 30vh (34vh on a narrow screen) because the toggle costs room too, and at 42vh the opened panel left the stage 149px — smaller than the state it was added to fix. Opened, the panel is now a little larger than the stage (273px against 219px at 910): that is a reader's deliberate choice, reversible in one click, where the regression was what everyone got without asking. - Every page after the first is published at 1.0x into showcase/pages/<category>/<group>/, named as a reader counts them: page 1 is the existing preview, so these start at -2. 70 images, 3.66 MiB. The scale follows what the viewer displays — a page renders at about 374 CSS pixels, so 595px covers it and 1.5x would publish pixels nothing shows; measured across all 33 documents, the same pages cost 6.27 MiB at 1.5x and 2.47 at 0.75x, which is already soft against that stage. "pages" joins the subtree list ShowcaseSync clears, so a renamed example cannot leave 70 orphans reachable by URL and absent from the manifest. - A row under the stage pages through them: "Page x of N" with its own small steps, deliberately unlike the large document arrows either side of the page, so the two kinds of movement never read as one control. Previous and Next, the arrow keys and a swipe keep moving between documents. The page index lives where every change of document passes and a redraw does not, so moving on starts the next document at its own first page. Paging reuses the load token, so a slow page cannot land under a document the reader has already left, and the alt text stopped telling a screen reader "first page" on page five. The page after the one on screen is warmed, behind the same data-saver gate as everything else the viewer fetches ahead. - The guards cover what the browser would otherwise be the only witness to: the panel's height budget and that its disclosure is not a growing flex item (at `flex: 1 1 100%` the button asked for the whole dialog and left the stage 40px); every family guide the panel links being a page that exists, keyed to a family the catalogue has, with its #fragment answering to a heading; and every published page being a file, one per page the card declares. 16 checks in ShowcaseSiteGuardTest, 46 cases in the viewer harness — including that a swipe still moves documents once a document has pages, which until now was only true by construction, and that stepping to a single-page document hands focus on before the page row disappears.
The site's HTML was hand-maintained beside a catalogue that already knew everything in it. A new example meant hand-editing three places in the page and hoping they agreed: the index a visitor without JavaScript gets, the JSON-LD a crawler reads, and the preset counts written out in prose. They did not agree — that index listed 38 of the 117 documents the site publishes, so two thirds of the catalogue reached a reader with no JavaScript as nothing at all. - `web/index.html` and `web/sitemap.xml` are now rendered from `web-src/` by `scripts/site/build.mjs`. Nothing else in `web/` is touched: the stylesheet, the scripts, the assets and the whole `showcase/` tree stay static, so the check below compares two pages instead of diffing a 25 MiB tree. GitHub Pages keeps serving the folder exactly as committed. - What the page says now comes from data. The no-JavaScript index carries **every** document, under its category and group, with the category headings keeping the ids the menu and the sitemap resolve to. The preset counts are computed by the same rule the site guard counts them with — distinct preset classes within a group, so a card that re-renders another's preset is not a second preset. The JSON-LD item list and the sitemap's documents keep their editorial names in `web-src/data/featured.json` while their URLs are resolved from the manifest, so a renamed PDF cannot leave a crawler pointed at nothing. - The release version is written down once, in `web-src/data/release.json`, and the build injects it into the seven spots on the page that inherit from no pom. `cut-release.ps1` moves those two values and rebuilds the pages rather than rewriting them, which a later build would undo. The rebuild runs after the catalogue sync, not beside the version bump: the pages are generated from `web/examples.json` too, and a rebuild before that sync would render them from a catalogue the cut then replaces — publishing an index missing whatever the release added. It also sits before the verify gate, so the site guards read the pages the release actually ships. The release commit stages both pages and the data together, since staging one without the others tags a release whose pages and source disagree, and the post-release flip back to `/blob/<branch>` rebuilds and stages them the same way. `VersionConsistencyGuardTest` is unchanged — it reads the built page, so it now holds generated text. - `scripts/site/build.test.mjs` fails when what is committed under `web/` is not what `web-src/` builds, and CI's guard job already runs every harness in that folder. The build refuses rather than publishes: a token the build does not produce, a value no template uses, a featured id naming no card, an empty featured list, and a card with no title or PDF each stop it by name — a missing title used to reach the page as the literal word "undefined". Line endings are always LF and the comparison is LF-normalised, so the check reads the same on a checkout that converts them. Verification: the site harness is 13 cases; `ShowcaseSiteGuardTest` (16), `VersionConsistencyGuardTest` (23) and the six guards that read the release script are green — 63 tests. The new refusals were each watched failing for their own reason: a page edited by hand, a token renamed, a token dropped, a featured id naming no card, and a count written into the template past the catalogue — 5 of 5. The release function was lifted by AST and run: both values move, the non-version field stays, a second bump is a no-op, and a renamed spot refuses by name and writes nothing. In the browser the regenerated page states v2.4.0 in the badge and the release block, keeps its filter pills, carries 117 links and 3 + 25 headings in the no-JavaScript index, escapes `Lists & Bullets`, keeps the 7 JSON-LD items and the viewer dialog, and logs no errors. The full reactor gate is green: 13 modules, BUILD SUCCESS, 782 core and 1779 qa tests among them, no failures.
…newcomer knows The home page opened on a heading four lines tall beside three cropped, overlapping previews, and on a phone it hid them altogether, so the first screen was all words. The guide to authoring templates sat above the catalogue, ahead of anything a visitor could use. And the catalogue's categories were named after how the examples module is organised — Features and Flagships — which tells someone arriving nothing about what they will get. - The menu is Templates · Examples · Documentation · Releases · GitHub, with a Get started button. - The hero is a short heading, one sentence, the Java minimum and the release, Get started and Browse templates, and one whole document with an Invoice / CV / Proposal / Report switch. Each entry is a catalogue card named in `web-src/data/featured.json`, and the build reads its preview, size, PDF and viewer address from that card, so the hero cannot point at a document the site does not publish. The first document is on the page as built, so a reader without JavaScript sees it and can open its PDF. - `web/home.js` shows the switch, and shows the link into the viewer only once `examples.js` announces that the viewer exists (`gallery-viewer-ready`): the viewer needs the catalogue and `<dialog>`, so a link shown when the script merely loaded would change the address and open nothing whenever the catalogue failed to load. A choice changes the picture, its caption and both links together once the new preview has decoded, and a preview that becomes ready after a newer choice is dropped; swapped at once, "Open PDF" pointed at the new document while the old picture was still on screen. `.hero-visual [hidden]` keeps both hidden until then — the stylesheet's one `[hidden]` rule was scoped to the viewer, and `display:flex` would have shown a switch that swaps nothing. - The heading's size follows the column it sits in, so it stays within two lines at every width from 320 to 1440 pixels; beside the document a fixed size had wrapped it to four lines at 1000 and 1024 and to three up to 1180. A phone gets one compact document instead of none, and on a short laptop screen the document gives up width so that it, its caption and the switch stay in the first screen. - What visitors read is renamed and nothing they link to is. Features are listed as Examples, the large complete documents as Showcase, and their group as Complete documents; those cards' visible, searchable tag is `showcase` rather than `flagship`, and three descriptions stop calling a document a flagship or a report a ready-to-ship template. Category ids are unchanged, so published paths, shared viewer addresses and the `#showcase`, `#templates-section`, `#features-section`, `#flagships-section` and `#install` anchors keep working. The gallery section drops the visible word Showcase so that it names one thing, the search box is labelled for the catalogue, and the JSON-LD drops "(flagship)". - The authoring guide moves into a Documentation block with its three links, the documentation index and the latest release, and the PowerPoint note moves from the hero into the feature grid. Inline code in those blocks was drawn in a colour set for the dark code panels and all but vanished in the light theme; it now follows the text. Long unbreakable calls no longer widen the feature cards past a 320px screen. The selected switch option keeps full text contrast, with its state carried by the border and tint. A visually hidden heading restores the page's heading order and gives its section the label its `aria-labelledby` already named. The rules for the fanned stack and the old callout are gone. Verification: the site harness is 18 cases and `home.test.mjs` 12, run against the hero parsed out of the built page; the viewer harness is green. Every new check was watched failing for its own reason against the final code, 12 of 12: the viewer link shown before the viewer exists, the caption swapping before the picture, a late preview applied over a newer choice, the viewer address left behind on a swap, the figure hook renamed to a name still containing `data-hero`, the link moved out of the figure, the event renamed in `examples.js`, the page not loading `home.js`, the switch no longer hidden by the build, swapped address segments, the PDF link rendered hidden, and a switch label used as a title. In the browser, with the fonts loaded, the heading holds two lines at 23 widths from 320 to 1440; there is no horizontal overflow from 320 up; inline code reads at 12.6–15.4:1 in both themes; and the switch ends inside the first screen at 1280×720 and 1366×768. The examples module is 93 tests green, and the full reactor gate — `ShowcaseSiteGuardTest` and `VersionConsistencyGuardTest` among it — is BUILD SUCCESS with 782 core and 1779 qa tests.
…utput format keeps The install section offered one coordinate, the engine, whatever a reader had come for. Someone after a template was handed a coordinate that renders none — and `graph-compose-templates` on its own brings neither the PDF backend nor the fonts most presets are drawn in. Someone after a deck or a Word file was handed nothing, and nothing on the page said what PowerPoint or Word keep of a document and what they lose. - The install section offers four scenarios, each as a labelled Maven and Gradle (Groovy DSL) coordinate at the release the page names: a PDF from your own layout (`graph-compose`, the engine and its PDF backend); ready-made templates (`graph-compose-bundle` — the engine and its PDF backend, the CV, cover letter, invoice, proposal, receipt and rota templates, the bundled fonts and colour emoji, with the independently versioned companions pinned for the reader); and an editable PowerPoint deck (`graph-compose-render-pptx`, Beta) or a Word document (`graph-compose-render-docx`), each added beside `graph-compose` or the bundle. The Gradle lines are labelled Groovy because the single quotes they carry do not compile in a `build.gradle.kts`. - An output formats block sets PDF, PowerPoint and Word side by side, with the limits a reader should know before choosing, taken from the backend capability matrix it links to. PDF is the reference: fonts you register or take from the bundle are embedded and subset (the standard fonts, the default Helvetica among them, are not), links, bookmarks and metadata are written, a document can be encrypted, and the deterministic mode renders byte-identical files. PowerPoint is geometry-identical with most vector drawing as editable shapes; a clip that would cut content, and inline SVG with clips, exact stroke styles or art outside its view box, become pictures; distinct per-corner radii, numeric dashes and radial gradients are approximated; there is no encryption, and only the first bookmark on a slide survives, as its name. Word gets real paragraphs, tables, block images, and headers and footers with live page numbers from page zones — but not the text header and footer (`DocumentHeaderFooter`), shapes, lines, barcodes, inline images, links or watermarks; it names its fonts without embedding them, and ignores hanging indents. The PowerPoint card the feature grid carried says nothing the block does not, and is gone. - `scripts/site/build.test.mjs` requires every scenario's coordinate in both forms, each at the release, and refuses a fonts or emoji coordinate outright. `VersionConsistencyGuardTest` reads the bare `graph-compose` coordinate alone, so a stale bundle or backend snippet would pass it; and requiring both forms is what catches a snippet the pattern can no longer read — a line slipped in between artifactId and version — which would otherwise go unchecked while its other form passed. - Three defects this markup leans on are fixed with it. Muted text in the light theme sat at 4.45:1 on the page background, under the 4.5:1 minimum for body text, and now reads at 6:1. The featured tiles' titles were an `h5` straight under an `h3`, and are an `h4`, with their size and weight unchanged. And a menu link to a section landed its heading under the sticky header — at 94px under a 141px header on a phone — and now lands below it, at every breakpoint where the header changes height. Verification: the site harness is 19 cases, `home.test.mjs` 12 and the viewer harness 46, all green. The coverage case was watched failing for each of its reasons, 4 of 4: a stale bundle version behind a line the pattern cannot read past, a Word coordinate a release behind, a fonts coordinate offered at the release, and the PowerPoint Gradle form removed. In the browser there is no horizontal overflow at 320, 390, 768, 1024 or 1440; the scenarios sit in one column and then two, the formats in one, two and three; no heading level is skipped anywhere on the page; muted text in the light theme reads at 6.03–6.46:1; and a menu link lands its heading at 243px below a 141px header on a 390px phone, at 273px below 146px at 768, and at 207px below 79px at 1440. The full reactor gate is BUILD SUCCESS, with `ShowcaseSiteGuardTest` and `VersionConsistencyGuardTest` green against the final page.
A document could be seen in the viewer and nowhere else. The viewer lives behind JavaScript and a fragment address, so a reader without a script got a bare PDF link, a crawler got the PDF and the home page, and no address led to a document's pages together with what reproducing it takes. - Each of the 117 documents gets a generated page at `<category>/<family>/<id>/`, the same three segments as its viewer address: every page of the document as an image at its own pixel size, each linking into the PDF (at that page, in the viewers that read `#page=`); the PDF, and the deck where one is published; what reproducing it takes; and the other documents of its family. Each page has a canonical address, a description, Open Graph tags and a JSON-LD block, and the sitemap lists them all beside the URLs it already had. - What a page tells a reader is not written a second time. The viewer's panel logic moves out of `createViewer` into a pure `panelModel(card, catalogue, release)` in `gallery-viewer.js`, with `pagePath(route)` as the one place a page's address is formatted. The viewer draws its panel from the model, and `scripts/site/build.mjs` loads the same script through `node:vm` to render the page's section. The site header, footer and theme scripts become partials shared by the home page and the document pages; the home page came out byte-identical from that step alone. - The build owns only what it wrote where it sits. A document page carries a generator mark, and the build counts a page as its own only when that page's canonical address also names the place it sits — a generated page copied elsewhere to start a page by hand keeps the mark, and is not the build's to delete. An owned page no card builds any more — a card renamed, moved or removed — is deleted with the directories it leaves empty, and `--check` fails on a page that is missing, stale or orphaned without changing the tree. A card id that is not address-safe, a category that would write into `assets/` or `showcase/`, two cards with one id, and a page image that is not a published PNG stop the build before it writes. - Links in. The no-JavaScript index links each document's page rather than its bare PDF, and each of its family headings carries the family's viewer address as its id, so a page's family link — which opens the viewer where a script runs — lands on that family's list where none does. The hero's second link goes to the page of the document on screen, so it works without a script, and the event `home.js` waited on to reveal a viewer link is gone from both sides. The viewer gains a Details link beside Open PDF, and the JSON-LD item lists name pages instead of PDFs. - The pages exposed two faults in the panel, fixed where both views read them. A family's worked snippet composes one preset, yet it was captioned "Compose it" on every card of the family: Blue Banner's reader was promised code that builds `BoxedSections`. Only the card of the preset the snippet calls `create(` on, matched as a whole name, says so now; the others say the snippet comes from the docs, and where it is published on a page other than the family guide — the CV snippet is on `using-templates.md`, the family starts at the quickstart — they link that page as well. And every value was set in monospace, sentences included; class names and paths are marked as literals and set as code, prose is not. The viewer also stops captioning every document "First page shown", which was untrue of the 33 documents it pages through. - `ShowcaseSiteGuardTest` finds the generated pages instead of reading a fixed list, resolves each link from the page's own directory — keeping the trailing slash that makes `../cv-b/` a page, refusing a root-relative link or one that climbs out of `web/`, and reading a link that is not a valid URI the way a browser does rather than failing on it — and checks an anchor against the page it points into, where a `#page=` after a PDF is not an anchor. The release cut stages the pages with the glob pathspec `:(glob)web/*/*/*/index.html`, in the release commit and in the post-release one, added only where the site build exists because a pathspec matching nothing fails the add; a new step in `release-script-check.yml` runs the function the cut calls over a rewritten, a deleted and an added page and fails if it stages anything else. Verification: the site build harness is 39 cases, the viewer harness 53 and the hero harness 10, all green. Each new check was watched failing for its own reason: 28 of 28 sabotages of the build, the page template, the viewer and the hero were caught by the case they target, with its message; a broken stylesheet link, a section anchor and an anchor into another page planted in one generated page, and a guard that crashed on an invalid URI, were each reported by ShowcaseSiteGuardTest (19 green); and the staging step, run in a scratch repository, passed on the real function and failed on a pathspec one level short, on one reaching `web/showcase/`, and on a post-release commit that stopped staging pages. Dry runs of the cut and of -PostReleaseOnly show the page pathspec in both `git add` lines and leave the tree untouched. In the browser, all 117 pages show no horizontal overflow at 320, 360, 390, 768, 1024 and 1440 pixels — after the fix for 11 feature pages whose descriptions are API calls with no break opportunity — text on a page reads at 6:1 or better in both themes, Details, Back and a page's family link each land where they name, and with scripts disabled a family address resolves to that family's heading and scrolls it below the header. The full reactor gate is BUILD SUCCESS.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The showcase renders a category section only while its filter shows it. After choosing Features, the Templates menu link — like every
#<category>-sectionanchor in the menu and the sitemap — changed the address and moved nothing, because#templates-sectionwas not in the DOM.The page also pointed at things the site does not publish, and nothing checked them:
HIGHLIGHT_IDSnamedcv-sidebar-portraitandcv-monogram-sidebar, whose cards are…-v2. The featured strip skips an unknown id without a sign, so it showed six of its eight tiles.invoice.pdf,proposal.pdfandfeatures/themes/invoice-custom-theme.pdf; none of them ships.JVM 21+while every module compiles withmaven.compiler.release17, the preset card counted 16 CV presets where 26 ship, and the template-authoring buttons linked thedevelopdocs from the released site.GitHub Pages serves
web/as committed, so each of these reached visitors directly.What changed
web/examples.js— section anchors. An anchor naming the gallery (#showcase) or a category section selects that filter first, then aligns the gallery heading below the sticky header, which on a phone is taller than the section's top padding.pushStaterecords the anchor, and focus moves to#showcase-title, as it would for a link the browser follows.replaceStatethe matching anchor, so a reload reopens the same view.web/examples.js— other fixes. The two featured ids gain-v2. A failed manifest load tells visitors to refresh or browse the examples on GitHub, instead of telling them to runShowcaseSync.web/index.html.invoice-modern-v2.pdfandproposal-modern-v2.pdf; the custom-theme entry and a duplicate cover-letter entry are removed.create(): ten presets have nocreate(BrandTheme).main, and the footer links the 2.0 migration guide instead of the 1.6 → 1.7 one.ShowcaseSiteGuardTest(core, documentation guards), added to the CI guard job and to the CONTRIBUTING list.web/**matches no path filter, so the always-running guard job is where a site-only pull request gets checked. A release cut's verify also runs it over the catalogue the cut has just regenerated. It fails when:examples.json;pdf,screenshotorpptxis not a file underweb/showcase/;index.html,sitemap.xmlorrobots.txtlinks to a site file that does not exist. This covers relativehref/srcand anyhttps://demchaav.github.io/GraphCompose/…address;#<category>-sectionanchor or a filter pill names a category the manifest lacks, or another anchor names no element inindex.html.Every list the guard reads must be non-empty, so a page whose shape moved fails instead of passing unchecked.
StrictJsonReaderandStrictJsonReaderTest(core test scope). The core test classpath has no JSON library. The reader refuses whatResponse.json()refuses: raw control characters in strings,+1,01, trailing commas and non-JSON whitespace. A manifest the page could not load therefore fails the guard instead of reading as a shorter catalogue.web/README.mddocuments three things:web/: the five version placescut-release.ps1rewrites on a final release (four of them held byVersionConsistencyGuardTest), theGH_BASEflip and catalogue sync, and-PostReleaseOnly;maindoes not wait for the Maven Central deployment, which is published by hand.CHANGELOG.mdopens## v2.4.1 — Plannedwith a Documentation entry.Verification
Build and tests.
./mvnw -B -ntp clean verify -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing,:graph-compose,:graph-compose-bundle,:graph-compose-qa,:graph-compose-coverage -amgives BUILD SUCCESS in 3 min: 3,188 tests, 0 failures, 2 skipped.CiGuardListGuardTestresolves the new name).ShowcaseSiteGuardTesthas 4 checks over the committedweb/files and 3 over parser inputs, andStrictJsonReaderTestadds 2.Sabotage. Each case patched the real file, ran
ShowcaseSiteGuardTestand restored the file. Every case went red and named the broken item:index.htmllink and a sitemap PDF gave three failures, listingtable-advancedX,receipt-modernX.png,weekly-scheduleX.pdfandbusiness-reportX.pdf.HIGHLIGHT_IDSconstant made the guard refuse to read a list that moved.{appended toexamples.json, or a form feed inside one of its strings, gavenot valid JSON.#feature-sectionin the sitemap and aflagshipsXpill were each named as landing nowhere.pdfset toindex.htmlwas reported as not a file undershowcase/.Browser, serving
web/locally at 1280 px and 375 px:features-sectionstayed in the DOM and the page did not move.index.html#features-sectionopens on the Features view. Back and Forward re-select the filter, and Back to the entry without an anchor shows All.#showcase-title.examples.jsonmissing, the error names GitHub.Not run: a real Tab key press (the preview pane was not drawing, so it took no key input) and the browser's own scroll restoration on reload.
Lane: site and test. Covers
web/, one core documentation guard with its JSON reader, and the CI guard list; no library code, no public API.