typograph-editor-documentation
    Preparing search index...

    Release Notes - Version 1.0.0-20261007

    Two fixes for pages that embed the editor: a document whose images came in without a size can be printed again, and the editor no longer asks for a clips file the page does not have. Toolbar controls explain themselves, and the editor's shortcuts work with Ctrl on Windows and Linux.

    • ⚠️ The editor's ids and classes are renamed — check pages that style or look up editor elements. Every id and class the editor's stylesheet styles now starts with typograph-: .menubar is .typograph-menubar, #menulayer is #typograph-menulayer, #toolbar is #typograph-toolbar. Your page's styles no longer reach the editor's elements and the editor's no longer reach yours. CSS or scripts aimed at an old name must use the new one — see Renamed ids and classes for the complete list. The gEditorUI keys (toolbar.select, contentbar.images, …), the menu ids and #typograph are unchanged.

    • A new look, and a dark mode. The editor's chrome — menus, toolbar, drawers, panels, dialogs — has a modern style, and can be dark: gEditorUI.set_theme('dark'), 'light' or 'system'. Only the chrome changes; the document keeps its own colours. See Theme in API-EditorUI.

    • Jump to a spread or page: typograph.show_spread() and typograph.show_page(). A host page can now bring any spread, or one page of it, into view — exactly as a click in the page bar does, with typograph_page_shown following. See Showing a page.

    • Hide swatches from the palettes. gEditorUI.hide_swatches([...uuids]) keeps those swatches out of every colour picker; show_swatches() brings them back. Elements that already use one keep it. See Swatch visibility.

    • The viewer can let users re-order and delete pages. Two new page-bar options for new Viewer({ page_bar: { … } }): allow_reorder (drag a page to a new place) and allow_delete (a delete control on the selected page, which asks before it deletes and is never offered for the last page). Both are off, so an existing viewer is unchanged. See Options.

    • A document can open on a spread. View → Start on a Spread makes page 1 a left-hand page, so pages pair 1-2, 3-4, 5-6 instead of 1, 2-3, 4-5 — for a brochure whose first two pages are one spread. Everything that depends on a page's side follows: elements set against the inside or outside edge, text aligned to the inside or outside, page numbers. View → Start on a Single Page turns it back; either is one step to undo. A host can do the same with typograph.set_first_page_left(true). When a document is saved as single pages with "keep pages together", its opening spread stays one spread.

    • A document with uploaded or linked fonts prints again. The PDF service turned a document away ("fonts[0].font_family — This value should not be blank") when its fonts were named only by id, name and link — which is how a page that embeds the editor usually lists them, and how every document came back after a save in the editor. The editor now keeps each font's family and style, and fills them in from the font file when they are missing. A document saved before this release is repaired by opening it and saving it again.

    • Deleting one page of a spread is announced. Removing a single page out of a two-page spread — from the Page menu, or the viewer's new delete control — sent no typograph_page_deleted; only deleting a whole spread did. It fires for both now, and says which page went: container_index and record_index (null for a whole spread). See Page Action Events.

    • Tooltips on the toolbar. Hovering a toolbar control shows its name, what it does and, where it has one, its shortcut. The tooltip stays inside the editor's own box when the editor is embedded in a page.

    • Ctrl works as ⌘ on Windows and Linux. Every shortcut that uses ⌘ on a Mac uses Ctrl elsewhere, and Ctrl-click does what ⌘-click does: removing an element from a selection on the canvas and in the layer list, among others. A Ctrl-click on empty canvas or on an element that is not selected leaves the selection alone; it used to clear it. Dragging with Ctrl held still moves, resizes or draws a selection box without snapping. The menus, the tooltips and the comment panel show the shortcut for the platform the editor runs on — Ctrl+Shift+G rather than ⇧⌘G.

    • One exception: the tab ruler. Its ⌃⌘T is Ctrl+Alt+T off a Mac, because Chrome keeps Ctrl+Shift+T for reopening a closed tab.

    • A document whose images have no size, or a size from the page, prints again. The PDF service turned the whole document away ("Extra attributes aren't allowed, "w" is unknown") when an image's size was saved in a short form it does not accept. That happened when a layout placed with replace_page_content / replace_spread_content carried an image without a size, and when a page passed image sizes as {w, h}. The editor now always saves the size in the form the PDF service expects. A document saved before this release is repaired by opening it and saving it again.

    • No get_clips.php request when the editor starts. At every start the editor warmed its clips list and, on a page without a clips list of its own, asked our demo server's php/get_clips.php — a failed request on every page that embeds the editor without that server, whatever its setup said. The editor now only warms a list the page provides; without one, clips are looked up when the Clips tab is opened. legacy_php_fallback: false in setup() now turns that lookup off too, like every other request to the demo server.

    These come from the PDF service, not from the editor, and reach users when it deploys:

    • Rotate pages in a PDF. A PDF job can turn chosen pages: "rotate_pages": [{"page": 3, "degrees": 90}], with 90, 180 or 270 degrees. Pages are numbered the way the pages option numbers them. The page is turned whole — its content, bleed and crop marks together; the document itself is not changed.
    • An empty page at the end. "add_blank_last_page": true adds one blank page after the last, the size of the last page and with the same crop marks — for printers that need an even count.
    • Canvas fix/manifest-font-family-style (PR #171): tg_manifest.font had no font_family / font_style, so every reader dropped them and every manifest the canvas wrote lacked them (document-schema: NotBlank; the generator registers fonts under them). Kept now by all three readers, and filled from the parsed file when a host leaves them out — family as add_fonts_from_urls() reports it, style via FontManager.style_for_url(), the variant key the file has in font_data. A font that did not load stays empty and the publisher refuses. Also: pagereplace read raw.family, which the wire does not have. Checked with a MyVivenda manifest: 10 font violations → 0.
    • Canvas pin dd32fd3 — TYP-818 (PR #172: page 1 on the left, per-page margins that travel) and the font fix above (PR #171), on top of b94e7cf: — fix/manifest-image-size-wire (PR #168), TYP-766 chrome colours from tokens (PR #153), TYP-805 typograph- prefix on the canvas names the editor styles (PR #165), TYP-795 scripts/platform (PR #162), TYP-819 show_spread() / show_page() (PR #169) and TYP-816/817 page-bar reorder and delete permissions (PR #170). Each of the three was brought up to date with a merge of master and squash-merged.
    • TYP-819 (canvas feat/TYP-819-show-spread): typograph.show_spread() / show_page() → document.show_spread(container_index, record_index) → PageBar.show(), which is a row click (select, then the page bar's own set_active_page()). Refused, with no change, for a missing spread or page, before setup, during a spine edit, or outside set_visible_page_indexes(). The page bar object exists whenever the canvas is set up, even hidden, so there is no no-page-bar path. Browser suite run_show_spread_tests(): 24 passed. Documented in docs/PageSelection.md.
    • TYP-820 (editor-ui feat/TYP-820-hide-swatches): src/swatchvisibility.ts holds the hidden set with no imports, so the pickers ask it without importing EditorUI; EditorUI.hide_swatches() / show_swatches() / get_hidden_swatches() delegate to it. Filtered: SwatchPanel.set_content() (rows keep their library-index ids, so click mapping is unchanged; the height counts shown rows; the click-handler loop skips missing rows) and the stop-colour lists of swatchdialog.ts and gradientdialog.ts. The POD dialog's "Document swatches" overview still lists all — it is a report, not a picker. Session only, nothing on the wire. Verified in the asset manager: 3 of 22 hidden → 19 rows, the right three missing; shown again → 22; is_changed() and the saved document unaffected. editor-ui has no unit-test runner.
    • TYP-821 / TYP-822 (document-generator and publisher-service TYP-821-822-pdf-page-options): PageOptionsInterface beside ConfigInterface (getRotatePages(), getAddBlankLastPage()), asked by instanceof so the two repos merge in either order; rotation is the page's /Rotate (TCPDF format Rotate); the blank page has the last page's size, bleed and crop marks and nothing else. Publisher: Config fields, validate() refuses a bad entry, migration Version20261006150000, CLI --rotate-pages / --add-blank-last-page. ⚠️ Merge order: document-generator → publisher-service with its lock bump → deploy publisher. api-client gets the fields only after that deploy — the publisher rejects unknown keys. Verified with real PDFs from the generate command.
    • TYP-816 / TYP-817 (canvas feat/TYP-816-817-viewer-page-bar, editor-ui branch of the same name): canvas setup({page_bar_allow_reorder, page_bar_allow_delete}) and set_page_bar_reorder() / set_page_bar_delete(); the viewer passes its page_bar.allow_*. In a viewer the rows were draggable with no drop handler behind them — now only with the option. The delete control asks inline (no browser dialog) and deletes through PageBar.remove_selected(), the editor Page menu's path. Browser suite run_viewer_page_bar_tests(): 25 passed. Deleting one page of a spread now also sends typograph_page_deleted, with container_index / record_index.
    • TYP-818 (document-schema firstPageLeft; canvas and editor-ui feat/TYP-818-first-page-left): canvas first_page_left (wire, written only when true), is_left_hand_page(n) as the one parity rule for repaginate(), page_side() and the page bar; set_first_page_left() is one structure undo step carrying the setting; keep-together writes the opening spread whole. Editor: View menu Start_on_a_Spread / Start_on_a_Single_Page (MenuAPI.md). ⚠️ Merge order: document-schema → lock bumps in converter-, publisher- and document-service → canvas → editor-ui; before the schema a template with the setting is refused ("firstPageLeft" is unknown — checked). The PDF generator needs nothing. Browser suite run_first_page_left_tests(): 24 passed.
    • TYP-766 / TYP-804 / TYP-805 (editor-ui PRs #104, #122, #123–#128, folded in through preview/20261006-new-look; canvas PRs #153, #165): the restyle with colour tokens and the dark palette (data-typograph-theme on <html>), every rule scoped under .typograph, and the typograph- prefix. docs/CSSNames.md lists the 210 renamed names, generated by comparing every class and id in stylesheets/style.css and in the editor and canvas sources before and after (editor master vs this branch; canvas b61ae87 vs 307b8ae). Not prefixed yet: ids used only for lookups and never styled (parmetersbarcontent, zoomin, undo, …) — Embedding.md said "every id", corrected. TYP-795's tooltip, written before the prefix, used a bare .is-visible; now .typograph-is-visible.
    • TYP-795 (editor-ui PR #120, folded in; canvas PR #162): canvas scripts/platform — is_mac, has_command, has_mac_control, shortcut_key, shortcut_label. control_defaults' kCommandkey/kControlkey/kOptionkey/kShiftkey are no longer read by the editor. src/tooltip.ts: one hover tooltip keyed on data-toolbar-group, mounted in editor_root() and clamped with layer_limits() like the toolbar popups; destroy() removes it and its listeners. Its entries were written into the 20261006 note on its branch and moved here when it was folded in.
    • Canvas fix/manifest-image-size-wire (merged): tg_manifest.image.toJSON() writes size as {width, height} from either spelling it holds, and {width: 0, height: 0} when there is none — document-schema models it as a required Page\Size and deserialises with ALLOW_EXTRA_ATTRIBUTES=false. The held value keeps its spelling and get_size() still answers {w, h}. pagereplace.ts filled a missing size in as {w:0, h:0}; it now uses the wire's spelling. set_manifest_data() and the document loader still TYPE the size as {w, h} — harmless now, since the output is translated. Checked with the publisher's ManifestValidator: {w, h} reproduces the two violations, {width, height} and 0 × 0 pass. tests/manifest_image_wire.test.ts.
    • editor-ui fix/clips-php-at-startup (folded into this branch): ContentBarClips.preload() returns early without get_clips_callback; the php branch of resolve_urls() asks host.use_php_fallback('php/get_clips.php') — it was the one php branch that did not — and treats a failed response as an empty list rather than a JSON parse error. The other direct php calls in the editor (get_manifest.php, save_page_json.php, get_templates.php, the upload and remove endpoints) still do not consult the gate; they run only on a user action, and the routing for them is Sebastiaan's feat/TYP-697-editor-providers.