oficina / sheets Public

fix(list): put Create against the account menu, where it was asked to be

Measured in a browser rather than reasoned about, and it took two goes.

rastrillo gives [rst-shell-account] margin-inline-start: auto. That
absorbs every pixel of free space in the bar, so the actions block —
which sits before the account in the markup, exactly where it should —
rendered hard left, tucked against the "Your sheets" nav link with the
account still out at the right edge. Not what was asked for and not
what it should look like.

The first fix gave the actions an auto margin of their own, which is
worse in an instructive way: flexbox splits free space equally between
auto margins, so the buttons came to rest dead centre of the bar.

The margin has to move rather than be added, and the override has to
beat 0-3-0, because the framework re-applies that auto margin inside
the same 800px media query at [rst-shell-topbar] [rst-shell-tail] >
[rst-shell-account]. A 0-2-0 sibling selector loses silently.

Only above 800px. Below it rastrillo stacks the tail into a column and
zeroes that margin itself, and an auto inline margin in a column flex
box shoves one item to the trailing edge — the bug its own comment
there describes. Same breakpoint, deliberately.

The wrapper carrying it is defined in the sheets page's block rather
than in layout.html, so a page that fills the block with nothing
contributes no element and the grid's bar is byte for byte what it was.

Also fixes tools/drive/drive.mjs, which this change broke: it filled
#title on the list page to name a new sheet, and that field is gone. It
now clicks the top bar's Create and renames from the title, which makes
the manual gate cover the rename submit as well.

Verified in a real browser at 1280x800, light and dark: create lands
"Untitled sheet", rename to "Q3 budget" persists and shows in the list,
the thumbnail still renders, and the console is clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 92654fa4382b088aeac9a8e9a040a5b4741f85d5 parent 9049c03
3 files changed, +63 −3
  • internal/sheets/static/sheets.css +42 −0
  • internal/sheets/templates/sheets.html +7 −0
  • tools/drive/drive.mjs +14 −3
diff --git a/internal/sheets/static/sheets.css b/internal/sheets/static/sheets.css
index 702ef5d..677b103 100644
--- a/internal/sheets/static/sheets.css
+++ b/internal/sheets/static/sheets.css
@@ -65,6 +65,48 @@
color: var(--rst-text);
}
+/* A page's own actions in the top bar, against the account menu.
+ *
+ * The auto margin has to live HERE and be taken off the account, not
+ * added alongside it. rastrillo gives [rst-shell-account] a
+ * margin-inline-start: auto, which absorbs all the bar's free space and
+ * would leave Create stranded back beside the nav — measured, not
+ * guessed: that is exactly how it first rendered. A second auto margin
+ * does not fix it either, because flexbox splits free space equally
+ * between auto margins and the buttons end up mid-bar.
+ *
+ * Only above 800px. Below it rastrillo stacks the tail into a column
+ * behind the Menu disclosure and zeroes that same margin itself, and an
+ * auto inline margin in a column flex box shoves one item to the
+ * trailing edge — the bug the framework's own comment at that rule
+ * describes. Same breakpoint as the framework's, deliberately: two
+ * shells with two breakpoints is two systems. */
+[data-topbar-actions] {
+ display: flex;
+ align-items: center;
+ gap: var(--rst-sp-2);
+}
+
+/* Both selectors are written against the framework's own shape, and
+ * both have to out-specify it. rastrillo re-applies the account's auto
+ * margin INSIDE this same media query at
+ * [rst-shell-topbar] [rst-shell-tail] > [rst-shell-account], which is
+ * 0-3-0; a plain [data-topbar-actions] ~ [rst-shell-account] is 0-2-0
+ * and loses, which is not a subtle failure — the buttons come to rest
+ * dead centre of the bar, because the two auto margins then split the
+ * free space between them. Measured, twice.
+ *
+ * The tail is display: contents up here, so these are siblings in the
+ * bar's own flex line even though they are nested in the markup. */
+@media (min-width: 800px) {
+ [rst-shell-topbar] [rst-shell-tail] > [data-topbar-actions] {
+ margin-inline-start: auto;
+ }
+ [rst-shell-topbar] [rst-shell-tail] > [data-topbar-actions] ~ [rst-shell-account] {
+ margin-inline-start: 0;
+ }
+}
+
/* The document title, in the top bar beside the brand. It truncates
* rather than wrapping: the bar is a fixed height, and a two-line title
* would push the account menu out of it.
diff --git a/internal/sheets/templates/sheets.html b/internal/sheets/templates/sheets.html
index ec7bc1e..4da71db 100644
--- a/internal/sheets/templates/sheets.html
+++ b/internal/sheets/templates/sheets.html
@@ -13,6 +13,12 @@
item in the File menu. */}}
{{define "topbaractions"}}
{{if .SignedIn}}
+{{/* The wrapper is what carries the auto margin that puts these
+ against the account menu — see sheets.css. It lives here rather
+ than in layout.html so that a page which fills this block with
+ nothing contributes no element at all, and the grid's bar is
+ exactly what it was. */}}
+<div data-topbar-actions>
{{/* Import first, Create last: Create is the primary action and sits
closest to the account menu, which is the corner the eye returns
to. A link rather than a form, because it opens a page rather
@@ -25,6 +31,7 @@
<form method="post" action="/sheets">
<button rst-btn="primary" type="submit">Create</button>
</form>
+</div>
{{end}}
{{end}}
diff --git a/tools/drive/drive.mjs b/tools/drive/drive.mjs
index 3946dfb..74215fd 100644
--- a/tools/drive/drive.mjs
+++ b/tools/drive/drive.mjs
@@ -78,16 +78,27 @@ if (!m) { console.log('FAIL: no magic link in the log'); process.exit(1); }
await page.goto(`${base}/auth/verify?token=${m[1]}`);
console.log('signed in as:', (await page.textContent('body')).includes('Signed in as') ? 'yes' : 'NO');
-// 2. Make a sheet.
+// 2. Make a sheet, from the top bar's Create button.
+//
+// This used to fill #title on the list page. That field is gone: the
+// list carries no forms now, Create posts an empty one, and the sheet
+// lands as "Untitled sheet" — so naming it is a rename, below.
await page.goto(base + '/sheets');
-await page.fill('#title', 'Browser drive');
// Not networkidle: the SSE stream is a long-lived connection, so the
// network on this page is never idle by design.
-await page.click('form[action="/sheets"] button[type=submit]');
+await page.click('[rst-shell-tail] form[action="/sheets"] button[type=submit]');
await page.waitForURL(/\/sheets\/\d+$/, { timeout: 15000 });
const url = page.url();
console.log('sheet url:', url);
+// 2b. Rename it from the top bar, which is now the only place a sheet
+// is ever named — and a real form submit, so this covers the
+// scriptless path too.
+await page.fill('[data-doc-title-input]', 'Browser drive');
+await page.press('[data-doc-title-input]', 'Enter');
+await page.waitForURL(/\/sheets\/\d+$/, { timeout: 15000 });
+console.log('renamed to:', JSON.stringify(await page.inputValue('[data-doc-title-input]')));
+
// 3. The grid must actually render.
await page.waitForSelector('.jexcel, .jss_worksheet, table', { timeout: 15000 });
console.log('grid rendered:', await page.locator('[data-sheet-grid] table').count() > 0);