oficina / sheets Public

Move the browser drives onto the estilo chrome; pin estilo 90f93a3

tools/drive/*.mjs drove the old markup: the rastrillo tail's Create, the
in-bar data-doc-title rename, rst-dropdown-menu panels, POST /mode and
the sheets_mode cookie, and the sign-in field's #address. They drive the
estilo bar, its editable title, [estilo-menu] panels, /appearance and
estilo_appearance now. estilo 90f93a3 hears Escape in the capture phase,
which the menubar needs (it stops keys from reaching the grid library).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 8df5f53d152c69e03f1ddb53dcfc957c684dec97 parent 523a877
8 files changed, +32 −30
  • go.mod +1 −1
  • go.sum +2 −2
  • tools/drive/chrome.mjs +7 −6
  • tools/drive/clipboard.mjs +3 −3
  • tools/drive/drive.mjs +5 −5
  • tools/drive/drop.mjs +5 −4
  • tools/drive/menus.mjs +6 −6
  • tools/drive/tableformat.mjs +3 −3

CI passed — run details

CI log
amadan: .amadan/timeout: this job's budget is 15m0s (this runner allows up to 30m0s)
=== vet ===
CGO_ENABLED=0 go vet ./...
go: downloading amadan.net/oficina/estilo.git v0.0.0-20260923234513-90f93a37e7a0
go: downloading amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9
=== fmt ===
=== build ===
CGO_ENABLED=0 go build ./...
=== migrations ===
rastrillo migration check
rastrillo migration check: models and migrations agree
=== go-test ===
CGO_ENABLED=0 go test -count=1 ./...
?   	amadan.net/oficina/sheets/cmd/sheets	[no test files]
ok  	amadan.net/oficina/sheets/internal/guardstest	0.135s
ok  	amadan.net/oficina/sheets/internal/sheets	10.896s
ok  	amadan.net/oficina/sheets/internal/sheets/calc	0.012s
ok  	amadan.net/oficina/sheets/internal/sheets/format	0.006s
ok  	amadan.net/oficina/sheets/internal/sheets/live	0.054s
ok  	amadan.net/oficina/sheets/internal/sheets/seal	0.003s
ok  	amadan.net/oficina/sheets/internal/sheets/sealtest	0.004s
ok  	amadan.net/oficina/sheets/internal/sheets/store	2.820s
ok  	amadan.net/oficina/sheets/internal/sheets/xlsx	0.313s
?   	amadan.net/oficina/sheets/tools/gencorpus	[no test files]
=== race ===
go test -race -count=1 ./internal/sheets/store/... ./internal/sheets/live/...
ok  	amadan.net/oficina/sheets/internal/sheets/store	63.723s
ok  	amadan.net/oficina/sheets/internal/sheets/live	1.081s
diff --git a/go.mod b/go.mod
index 574ffb5..246d638 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module amadan.net/oficina/sheets
go 1.25.0
require (
- amadan.net/oficina/estilo.git v0.0.0-20260923231144-50567b151510
+ amadan.net/oficina/estilo.git v0.0.0-20260923234513-90f93a37e7a0
amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489
amadan.net/rastrillo/rastrillo v0.26.1-0.20260908075707-29e4491631bd
diff --git a/go.sum b/go.sum
index 7b2b05f..3ecdcc1 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-amadan.net/oficina/estilo.git v0.0.0-20260923231144-50567b151510 h1:fLpBbThwaU/dwP0A2yDrM1iQBt/82qLr49F2TVI12gE=
-amadan.net/oficina/estilo.git v0.0.0-20260923231144-50567b151510/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
+amadan.net/oficina/estilo.git v0.0.0-20260923234513-90f93a37e7a0 h1:deHC+KLkfDplVX73ddgom1ncjadu1nquhifudCXAAi4=
+amadan.net/oficina/estilo.git v0.0.0-20260923234513-90f93a37e7a0/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9 h1:K/PaOVdQqRMwifPD6ipiwLeGR5nbFSxm9FxjrDJlZcg=
amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9/go.mod h1:YroavXe43Wietec+po8mtSk4a9Bir2h31Q3iAf4MWxI=
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489 h1:RPKtyry/71i06o8L+gjAaUyexbmG56kO1YPs601URAM=
diff --git a/tools/drive/chrome.mjs b/tools/drive/chrome.mjs
index 95aaa11..62ccdf9 100644
--- a/tools/drive/chrome.mjs
+++ b/tools/drive/chrome.mjs
@@ -73,12 +73,12 @@ await page.waitForURL(/\/sheets/);
* scriptless path as well as the button. */
await Promise.all([
page.waitForURL(/\/sheets\/\d+$/),
- page.click('[rst-shell-tail] form[action="/sheets"] button[type=submit]'),
+ page.click('[estilo-bar-actions] form[action="/sheets"] button[type=submit]'),
]);
-await page.fill('[data-doc-title-input]', 'Commission Q2');
+await page.fill('[estilo-bar-title-input]', 'Commission Q2');
await Promise.all([
page.waitForURL(/\/sheets\/\d+$/),
- page.press('[data-doc-title-input]', 'Enter'),
+ page.press('[estilo-bar-title-input]', 'Enter'),
]);
const url = page.url();
await page.waitForFunction(() => window.SheetsGrid && window.SheetsGrid.worksheet());
@@ -237,11 +237,12 @@ console.log('\n— screenshots and reflow');
await page.evaluate(() => document.activeElement.blur());
await page.screenshot({ path: `${OUT}/sheet-light.png` });
-/* Dark, chosen explicitly through the form the account menu posts. */
+/* Dark, chosen explicitly through the form the account menu posts
+ * (estilo's /appearance, which replaced /mode). */
await page.evaluate(() => {
const f = document.createElement('form');
- f.method = 'post'; f.action = '/mode';
- f.innerHTML = '<input name="return" value="' + location.pathname + '"><input name="mode" value="dark">';
+ f.method = 'post'; f.action = '/appearance';
+ f.innerHTML = '<input name="return_to" value="' + location.pathname + '"><input name="mode" value="dark">';
document.body.appendChild(f); f.submit();
});
await page.waitForFunction(() => window.SheetsGrid && window.SheetsGrid.worksheet());
diff --git a/tools/drive/clipboard.mjs b/tools/drive/clipboard.mjs
index 709b96a..a240fdd 100644
--- a/tools/drive/clipboard.mjs
+++ b/tools/drive/clipboard.mjs
@@ -36,9 +36,9 @@ const mm=[...fs.readFileSync(process.env.SHEETS_LOG,'utf8').matchAll(/http:\/\/1
await page.goto(mm[0]); await page.waitForURL(/\/sheets/);
// Create from the top bar, then name it by renaming: the list's #title
// field is gone, so a sheet is born "Untitled sheet" and named after.
-await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.click('[rst-shell-tail] form[action="/sheets"] button[type=submit]')]);
-await page.fill('[data-doc-title-input]','Clip2');
-await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.press('[data-doc-title-input]','Enter')]);
+await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.click('[estilo-bar-actions] form[action="/sheets"] button[type=submit]')]);
+await page.fill('[estilo-bar-title-input]','Clip2');
+await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.press('[estilo-bar-title-input]','Enter')]);
await page.waitForFunction(()=>window.SheetsGrid&&window.SheetsGrid.worksheet());
const w=(r,c,v)=>page.evaluate(([r,c,v])=>window.SheetsGrid.write(r,c,v),[r,c,v]);
const sel=(x1,y1,x2,y2)=>page.evaluate(([a,b,c,d])=>window.SheetsGrid.worksheet().updateSelectionFromCoords(a,b,c,d),[x1,y1,x2,y2]);
diff --git a/tools/drive/drive.mjs b/tools/drive/drive.mjs
index 01bb025..311dbd1 100644
--- a/tools/drive/drive.mjs
+++ b/tools/drive/drive.mjs
@@ -75,7 +75,7 @@ page.on('pageerror', (e) => errors.push('pageerror: ' + e.message));
// 1. Sign in, the way a person does.
const before = fs.readFileSync(LOG, 'utf8').length;
await page.goto(base + '/signin');
-await page.fill('#address', 'paul@example.com');
+await page.fill('input[name=address]', 'paul@example.com');
await page.click('form[action="/signin"] button[type=submit]');
await page.waitForURL(/signin\?sent=1/, { timeout: 15000 });
@@ -93,7 +93,7 @@ console.log('signed in as:', (await page.textContent('body')).includes('Signed i
await page.goto(base + '/sheets');
// Not networkidle: the SSE stream is a long-lived connection, so the
// network on this page is never idle by design.
-await page.click('[rst-shell-tail] form[action="/sheets"] button[type=submit]');
+await page.click('[estilo-bar-actions] form[action="/sheets"] button[type=submit]');
await page.waitForURL(/\/sheets\/\d+$/, { timeout: 15000 });
const url = page.url();
console.log('sheet url:', url);
@@ -101,10 +101,10 @@ 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.fill('[estilo-bar-title-input]', 'Browser drive');
+await page.press('[estilo-bar-title-input]', 'Enter');
await page.waitForURL(/\/sheets\/\d+$/, { timeout: 15000 });
-console.log('renamed to:', JSON.stringify(await page.inputValue('[data-doc-title-input]')));
+console.log('renamed to:', JSON.stringify(await page.inputValue('[estilo-bar-title-input]')));
// 3. The grid must actually render.
await page.waitForSelector('.jexcel, .jss_worksheet, table', { timeout: 15000 });
diff --git a/tools/drive/drop.mjs b/tools/drive/drop.mjs
index c96deba..1751bd8 100644
--- a/tools/drive/drop.mjs
+++ b/tools/drive/drop.mjs
@@ -71,7 +71,7 @@ function check(name, ok, detail) {
* loopback origin with no SMTP — see internal/sheets/auth.go. */
const before = fs.readFileSync(LOG, 'utf8').length;
await page.goto(base + '/signin');
-await page.fill('#address', 'paul@example.com');
+await page.fill('input[name=address]', 'paul@example.com');
await page.click('form[action="/signin"] button[type=submit]');
await page.waitForURL(/signin\?sent=1/, { timeout: 15000 });
const link = fs.readFileSync(LOG, 'utf8').slice(before).match(/auth\/verify\?token=([A-Za-z0-9_-]+)/);
@@ -224,11 +224,12 @@ check('...and the refusal does not outlive the dismissal',
console.log('\n— dark');
-await ctx.addCookies([{ name: 'sheets_mode', value: 'dark', url: base }]);
+await ctx.addCookies([{ name: 'estilo_appearance', value: 'dark', url: base }]);
await page.goto(base + '/sheets');
await page.evaluate(helper);
await drag(1);
-/* The cookie is sheets_mode. Naming it "mode" renders the light page
+/* The cookie is estilo_appearance (estilo's chrome.AppearanceCookie; it
+ * was sheets_mode until round 2). Naming it wrong renders the light page
* and the screenshot proves nothing — which it once did, and the only
* tell was that the two images were pixel-identical. */
check('dark really is dark', (await page.getAttribute('html', 'data-theme')) === 'dark');
@@ -236,7 +237,7 @@ await page.screenshot({ path: OUT + '/list-dragging-dark.png' });
console.log('\n— the drop imports');
-await ctx.addCookies([{ name: 'sheets_mode', value: 'light', url: base }]);
+await ctx.addCookies([{ name: 'estilo_appearance', value: 'light', url: base }]);
await page.goto(base + '/sheets');
await page.evaluate(helper);
await page.evaluate(([b64]) => {
diff --git a/tools/drive/menus.mjs b/tools/drive/menus.mjs
index d508b85..2d077f3 100644
--- a/tools/drive/menus.mjs
+++ b/tools/drive/menus.mjs
@@ -51,9 +51,9 @@ await page.goto(mm[0]); await page.waitForURL(/\/sheets/);
// No sheets yet: Create from the top bar, then name it by renaming.
// The list's #title field is gone — a sheet is born "Untitled sheet".
if(!(await page.$('[data-sheet-list]'))){
- await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.click('[rst-shell-tail] form[action="/sheets"] button[type=submit]')]);
- await page.fill('[data-doc-title-input]','Audit');
- await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.press('[data-doc-title-input]','Enter')]);
+ await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.click('[estilo-bar-actions] form[action="/sheets"] button[type=submit]')]);
+ await page.fill('[estilo-bar-title-input]','Audit');
+ await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.press('[estilo-bar-title-input]','Enter')]);
}
else {await page.click('[data-sheet-list] [rst-row-main] a'); await page.waitForURL(/\/sheets\/\d+$/);}
const url=page.url();
@@ -75,7 +75,7 @@ const items=await page.evaluate(()=>{
const out=[];
document.querySelectorAll('[data-menubar] > details').forEach((d,mi)=>{
const menu=d.querySelector(':scope > summary').textContent.trim();
- d.querySelectorAll('[rst-dropdown-menu] a, [rst-dropdown-menu] button').forEach(el=>{
+ d.querySelectorAll('[estilo-menu] a, [estilo-menu] button').forEach(el=>{
out.push({menu, mi, label:el.textContent.trim().replace(/\s+/g,' ').slice(0,28),
kind:el.tagName, cmd:el.dataset.cmd||'', value:el.dataset.value!==undefined?el.dataset.value:(el.dataset.fn||''),
href:el.getAttribute('href')||'', inForm:!!el.closest('form'),
@@ -98,8 +98,8 @@ for (const it of items) {
try {
await page.click(`[data-menubar] > details:nth-child(${it.mi+1}) > summary`);
if (it.sub) await page.click(`[data-menubar] > details:nth-child(${it.mi+1}) details[rst-menu-group] >> summary:text-is(${JSON.stringify(it.sub)})`);
- const sel=`[data-menubar] > details:nth-child(${it.mi+1}) [rst-dropdown-menu] [data-cmd="${it.cmd}"]`+
- (it.value?`[data-value="${it.value}"], [data-menubar] > details:nth-child(${it.mi+1}) [rst-dropdown-menu] [data-cmd="${it.cmd}"][data-fn="${it.value}"]`:'');
+ const sel=`[data-menubar] > details:nth-child(${it.mi+1}) [estilo-menu] [data-cmd="${it.cmd}"]`+
+ (it.value?`[data-value="${it.value}"], [data-menubar] > details:nth-child(${it.mi+1}) [estilo-menu] [data-cmd="${it.cmd}"][data-fn="${it.value}"]`:'');
await page.click(sel+' >> nth=0', {timeout:5000});
} catch(e){ console.log(`ERR ${it.menu} > ${it.label} (${String(e).split('\n')[0].slice(0,70)})`); continue; }
await page.waitForTimeout(600);
diff --git a/tools/drive/tableformat.mjs b/tools/drive/tableformat.mjs
index 040da0f..535bf8e 100644
--- a/tools/drive/tableformat.mjs
+++ b/tools/drive/tableformat.mjs
@@ -28,9 +28,9 @@ const mm=[...fs.readFileSync(process.env.SHEETS_LOG,'utf8').matchAll(/http:\/\/1
await page.goto(mm[0]); await page.waitForURL(/\/sheets/);
// Create from the top bar, then name it by renaming: the list's #title
// field is gone, so a sheet is born "Untitled sheet" and named after.
-await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.click('[rst-shell-tail] form[action="/sheets"] button[type=submit]')]);
-await page.fill('[data-doc-title-input]','Table format');
-await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.press('[data-doc-title-input]','Enter')]);
+await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.click('[estilo-bar-actions] form[action="/sheets"] button[type=submit]')]);
+await page.fill('[estilo-bar-title-input]','Table format');
+await Promise.all([page.waitForURL(/\/sheets\/\d+$/),page.press('[estilo-bar-title-input]','Enter')]);
const url=page.url();
await page.waitForFunction(()=>window.SheetsGrid&&window.SheetsGrid.worksheet());
for(const [r,c,v] of [[0,0,'Region'],[0,1,'Revenue'],[1,0,'North'],[1,1,'1200'],[2,0,'South'],[2,1,'900']])