{{/* show — GET /d/{sqid}, the document read view (spec §6.1/§6.2). handlers.go's show builds the view data: Title is the document's own title, printed as-is the same way list.html's row titles are (data, not authored copy — not resolved through rastrillo.T); HTML is Store.HTML's server-rendered replay of the CRDT's ProseMirror fragment through wazero, never a database column, and is empty for a document with no updates yet — an ordinary empty canvas below, not an error. It is template.HTML, so {{.HTML}} below is written out unescaped on purpose — see Store.HTML's own doc comment for where that trust decision is made and why. [docs-canvas]/[docs-page]/[docs-text] are this app's own vocabulary (SKILL.md §7: "ui is rastrillo's vocabulary, not yours"), not rastrillo's, so they are not spelled rst-*, same reasoning as list.html's [docs-thumb]. The canvas opts out of the surrounding theme on purpose (spec §10.2, the brief's canvas constraint): a document is a piece of paper, and paper does not follow the reader's dark-mode preference the way the chrome around it does. color-scheme: light plus literal colours below — never var(--rst-surface) or var(--rst-text), which would make the "page" repaint itself dark along with everything else on screen. [docs-page] is one page-shaped container, and it stays one in both view modes. Paged draws desk-coloured gaps BETWEEN pages as widget decorations inside the text flow (fixtures/pages.mjs) rather than repeating this element. This comment used to say a later version would repeat [docs-page] once per page. It cannot: ProseMirror owns everything inside the editable element ({mount: el}), so its children cannot be wrapped in sibling page boxes without taking the document away from it. Repeating the box is a layout-engine shape; a decoration is a pagination-as-a-view shape, and a view is all pagination can be while page breaks are computed from measured heights and never persisted. Writes the bare rst-* attribute grammar throughout — see list.html's own comment on why, and render_test.go's TestOurTemplatesUseTheAttributeGrammar, which holds it against this file too. */}} {{/* The bar is the layout's (views/layout.html, chrome.go). On this screen it carries the document's title, editable in place for anyone who may write (estilo's title slot, POST /d/{sqid}/title), and the owner's "Share…" action. Sign out is in the account menu and nowhere else. */}} {{define "content"}} {{/* This page has no [rst-page-header]. It used to, and what it held is not gone — every piece of it moved to where its equivalent lives in the two applications this layout language comes from. What follows is only the map. The route back to the list was [docs-back], a breadcrumb above the title, and is now the bar's brand link, "Docs" — the same control on every screen rather than one this page draws for itself. Until [docs-back] landed this page had no in-app navigation at all, so arriving by a bookmark or a mailed link left a person with no route to their own list. The title is in the bar beside the brand, editable in place. Sign-out (POST /signout, auth.go) is in the bar's account menu and nowhere else: it used to be the last item of File as well. The exports are items in the File menu below. They used to be two ghost buttons pinned to the formatting bar's trailing end, which is what Paul's brief called the "random buttons": export is a document-level verb, and the formatting bar is a group of run-level controls, so it was the one place a person would not look. They are still ordinary links to ordinary URLs, so both still work with no JavaScript at all — spec §6.2 — and both are GETs of a document the signed-in person may already read, so neither needs a form or a CSRF token. download tells the browser to save rather than navigate; the filename it uses comes from the server's Content-Disposition, not from this attribute, because a title is arbitrary text a person typed and sanitising it belongs server-side (handlers.go's exportFilename). */}} {{/* The menu bar (internal/docs/menu.go for the model and the copy, fixtures/menu.mjs for the keyboard behaviour). Each menu is a
, and that is the load-bearing decision on this screen rather than a convenience. The export links work today with scripts disabled and the whole read view is built on that guarantee (editor_view_test.go's TestShowRendersContentWithoutScript); a JS-driven role="menubar" would have moved the only route to a person's own document behind a control that never opens for them.
opens with no script at all, and name="estilo-menus" makes the five an exclusive group — opening one closes the rest — with no state for anything to keep. It is the SAME group as the bar's account menu, switcher and Menu sheet, so opening File closes the account menu and the other way round (spec §7.1). It stays a disclosure rather than being relabelled role="menubar" with role="menuitem" children. is a real disclosure control that browsers and assistive technology already handle correctly; declaring the menu pattern promises typeahead and submenu semantics, and delivering less of it than that is worse for a screen reader user than the honest disclosure it replaced. fixtures/menu.mjs only ADDS to this: arrow keys between and within menus, Escape to close, and a roving tabindex so the bar is one tab stop. With no JavaScript every one of these still opens, and every item that is a link or a form still works. There are no submenus anywhere, and there will not be: a nested
inside a name= group collapses its parent (rastrillo's rst-menus trap, which the Sheets session hit first). So "Download as PDF" is a sibling of "New document" rather than a child of a "Download" submenu. */}} {{/* [docs-chrome] is the single sticky element wrapping both bars. It was styled and never rendered for one review cycle, which cost two things at once: [docs-bar] had been `position: sticky` before this branch and stopped being, so the formatting bar scrolled away — a regression against main sitting under a 30-line comment describing the fix that replaced it; and File → Print printed the menu bar and the toolbar across the top of page one, because the print rule hides [docs-chrome] and nothing carried it. Both rules were correct. Neither had an element. */}}
{{/* The formatting bar (fixtures/toolbar.mjs). Rendered server-side, with every label resolved from the catalog (render.go's docs.editor.toolbar.*) — the bundle carries no copy of its own, the same rule [estilo-save-status] below already follows. It is rendered even for a reader with no JavaScript, where it does nothing at all. That is a deliberate choice and the weaker of the two: the alternative is rendering it only when the editor mounts, which means the page reflows under a person the moment the bundle runs, and means the server template has to know something it cannot know. A visible-but-inert bar is the smaller harm, and the controls are all disabled until the toolbar's own plugin enables them, so nothing here invites a click that would silently do nothing. role="toolbar" with an accessible name, and each control's name from the catalog rather than from its glyph: a button whose only content is a "B" announces as "B" to a screen reader without one. */}}
{{/* The three selects opt into rastrillo's searchable-select enhancement (ui/select.js, already served by render.go's vendoredStaticFS and linked by the shell's own layout). Paul's brief: "selects should be Rastrillo design system searchable selects". data-rst-select is the opt-in for a HAND-WRITTEN select — ui/partials/field-select.html emits it only past ten options, on the framework's judgement that search over a handful of items is furniture rather than help. All three take it here anyway. Two reasons: they sit side by side in one group, and one combobox flanked by two native selects looks worse than either extreme; and all three are growth surfaces — Size has eleven options already, Style grows with named styles, and the face list is built to grow. If the filter over three fonts reads as silly in the morning it is one attribute to take off, which is why the tension is recorded rather than resolved silently. The three data-rst-select-* strings are the framework's own catalog keys, resolved server-side (export.go) because select.js builds its markup in the browser where the catalog is out of reach. Still `disabled` until the editor mounts, like every other control in this bar — and select.js does NOT mirror that, so toolbar.mjs mirrors it onto the combobox by hand. Left alone, the enhancement ships a control that looks usable, accepts focus and typing, and drives a disabled select behind it. */}}
{{/* Alignment: the first block-level formatting, so it gets a group of its own between the inline marks and the list controls, which is also where the block it acts on sits in the reading order. aria-pressed rather than a select, and exactly one of the three is pressed at any moment — toolbar.mjs's sync sets it from the block the cursor is in, the same way Bold reads the marks there. [docs-tool]'s 28px box already clears WCAG 2.2 AA's 24x24 minimum. These three are icon-only, so the pressed state is shown by the inset bar under the glyph rather than by the weight change that serves B/I/<> — see that rule's own comment, which used to claim the weight covered every button and did not (§10.3: colour never carries meaning alone). */}}
{{/* closes [docs-chrome] */}} {{/* data-view is the person's paged/infinite preference, read from the docs-view cookie server-side (menu.go's viewMode) so the FIRST paint is already right. Doing it in the bundle instead would mean anyone who prefers paged watches an infinite document reflow into pages a moment after it appears. data-page-label is the page-number template ("Page {n}") for fixtures/pages.mjs to stamp into each gap. Copy, so it comes from the catalog here rather than being a literal in the bundle — the rule the toolbar and the save-status indicator already follow. */}}
{{/* docs-editor is the ProseMirror mount point (fixtures/editor-entry.js, Task 6): the same [docs-text] container the scriptless read view already renders into, not a second element beside it. ProseMirror replaces this container's contents only once it mounts, so the server-rendered {{.HTML}} below is what a scriptless reader sees and what a scripted reader sees until the editor takes over — spec §6.2, and editor_view_test.go's own TestShowRendersContentWithoutScript pins it. data-sqid is the bundle's only way to learn which document's update log (/d/{{.Sqid}}/updates) to load and post to. */}} {{/* A reader gets the same container without the mount point, the data-sqid or (below) the script — absent, not disabled, because a disabled editor is one CSS rule from being an editor again. The server-rendered HTML is identical either way, so this is the scriptless read view the whole page already falls back to. */}} {{if .CanWrite}}
{{.HTML}}
{{else}}
{{.HTML}}
{{end}}
{{/* The Tools menu's two dialogs. Native , so the browser provides modality, Escape, focus trapping and the backdrop — four things that are easy to get subtly wrong by hand and none of which is the interesting part of either feature. Rendered on every document page and empty until opened: the copy is here because every user-visible string is a catalog key, and the bundle fills in numbers (fixtures/wordcount.mjs) and rows (fixtures/shortcuts.mjs) only. With no JavaScript neither can open, which is honest — there is no editor to count or to send keys to. Outside [docs-editor], like every other piece of chrome on this screen: ProseMirror's {mount: el} takes over everything inside that element. */}}

{{.Dialogs.WordCountTitle}}

{{.Dialogs.WordCountWords}}
0
{{.Dialogs.WordCountCharacters}}
0
{{.Dialogs.WordCountCharactersNoSpaces}}
0
{{.Dialogs.WordCountParagraphs}}
0
{{/* Hidden until something is selected — a row reading "0" invites a reader to work out whether that is a count or an absence. */}}

{{.Dialogs.ShortcutsTitle}}

{{/* The bindings no menu item carries, so shortcuts.mjs cannot read them off the bar. Rendered here as data for it to pick up rather than as literals in the bundle. */}}
{{template "estilo-save-status" .SaveStatus}} {{/* type="module" defer: modules are deferred by default, but stated explicitly here so load order (after the DOM, before DOMContentLoaded) doesn't depend on that default going unnoticed by a future edit. Served without a session (assets.go's staticHandler, ahead of the RequireSession group) and, if it throws, editor-entry.js's own catch marks [docs-editor] data-editor-failed rather than clearing it — the read view above stays exactly as rendered. */}} {{if .CanWrite}}{{end}} {{template "docs-jumpto" .}} {{end}}