{{define "head"}} {{/* Vendored, in dependency order, and deferred so neither blocks the first paint. defer preserves document order, so each module below is defined before the one that calls it: live.js registers window.SheetsLive and paint.js window.SheetsPaint; grid.js calls both once the window has loaded; menubar.js and commands.js wire the chrome and read window.SheetsGrid, which grid.js publishes. */}} {{end}} {{/* The open sheet's title, and for anyone who may edit it the rename, are the bar's (estilo's editable title slot, chrome.TitleEdit, built in handleSheetPage). It is still a real form posting to /sheets/{id}/rename, so Enter renames with scripting off, and the Rename button is visually hidden until the field has focus. Escape restores the title and leaves the field (estilo chrome.js), which fixes the sticky focus look the audit found. A reader sees the title read-only. */}} {{define "main"}} {{/* The grid gets its own
, without rst-page's padding and max width: a spreadsheet is the one screen in this app that should fill the viewport. Everything else uses the layout's default. */}}
{{template "content" .}}
{{end}} {{/* --- Glyphs ------------------------------------------------------- Written here rather than fetched with {{icon}}. rastrillo vendors twelve Lucide slugs — alert-triangle, check, check-circle, chevron-down, help-circle, info, kebab, menu, plus, search, x, x-circle — and rastrillo.Icon returns an EMPTY STRING for a slug it does not have, with no error. So {{icon "bold"}} is not a missing icon, it is a button with no label and no complaint, which is the same silent-failure shape as an invented CSS token. Bold and italic are letters, because that is what they are on every spreadsheet anybody has used. The three alignments are rules, because "left" as a word is longer than the button. */}} {{define "glyph-align-left"}}{{end}} {{define "glyph-align-center"}}{{end}} {{define "glyph-align-right"}}{{end}} {{define "glyph-undo"}}{{end}} {{define "content"}} {{/* Row B — the menu bar and the toolbar share one row. Six short words are about 300px; a row holding only them wastes two-thirds of itself. They wrap onto separate rows below ~900px, which is also what keeps the chrome inside the 320px reflow budget. */}}
{{if .Data.MayWrite}} {{/* The toolbar. Hidden until commands.js wires it: every control posts, so an unwired one is a button that does nothing. role="toolbar" with a roving tabindex, so the whole strip is one tab stop rather than fourteen — the same shape the Docs app uses, deliberately, so the two apps behave alike. */}} {{end}}
{{/* Row C — the name box and the formula bar, full width. The formula bar shows the selected cell's INPUT while the grid shows its computed value, which is the difference between a spreadsheet and a table of numbers. It is also the reliable way to see a formula: the in-cell editor swap is best effort. */}}
Selected cell contents
{{template "contrast-notice" .Data.Warned}}
{{/* Row D — the tabs along the bottom, and the status that used to sit in the strip above the grid. One tab today, rendered as a link to the page it is already on, so the strip is real markup rather than a picture of tabs. There is deliberately no "+": nothing in this build creates, names or switches a tab, and spec §14 makes "a second tab is reachable in the UI" the trigger for multi-worksheet import. A + that did nothing would fake that trigger. */}}
{{/* Deliberately NOT role="status". It changes on every arrow key, and a live region announcing "3 cells" after each one talks over the cell content a screen reader is trying to read. It is a glance, and the selection itself is what announces. */}} {{/* The one place this app can say something transient. role="status" is right here and wrong on the selection size beside it: this changes when you ask for something and it did not happen, which is exactly what a person needs read out, where the selection size changes on every arrow key and would talk over the cell. */}} Connecting
{{end}}