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>
8 files changed,
+32
−30
go.mod+1 −1go.sum+2 −2tools/drive/chrome.mjs+7 −6tools/drive/clipboard.mjs+3 −3tools/drive/drive.mjs+5 −5tools/drive/drop.mjs+5 −4tools/drive/menus.mjs+6 −6tools/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']]) |