Public: anyone can read this; the crew can change it.
A look keeps its page's size: scrollWidth, scrollHeight, innerWidth and innerHeight are stored on it and read back #10205
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/10202-a-look-keeps-its-page-s-size-scrollwidth → main 1 commit fork
5 files
changed +106
−2
src/core/looks.ts +41 −2
| @@ -109,6 +109,16 @@ export interface Look { | ||
| 109 | 109 | readonly attachedBy: string | null; |
| 110 | 110 | /** The one line that says what an attached look shows. */ |
| 111 | 111 | readonly note: string | null; |
| 112 | /** The page's own size when it was pictured; absent when it was not kept. */ | |
| 113 | readonly size?: LookSize; | |
| 114 | } | |
| 115 | ||
| 116 | /** The document's scroll size and the window's inner size, in CSS pixels. */ | |
| 117 | export interface LookSize { | |
| 118 | readonly scrollWidth: number; | |
| 119 | readonly scrollHeight: number; | |
| 120 | readonly innerWidth: number; | |
| 121 | readonly innerHeight: number; | |
| 112 | 122 | } |
| 113 | 123 | |
| 114 | 124 | interface Row { |
| @@ -125,6 +135,10 @@ interface Row { | ||
| 125 | 135 | taken_as: "owner" | "nobody"; |
| 126 | 136 | attached_by: string | null; |
| 127 | 137 | note: string | null; |
| 138 | scroll_width: number | null; | |
| 139 | scroll_height: number | null; | |
| 140 | inner_width: number | null; | |
| 141 | inner_height: number | null; | |
| 128 | 142 | } |
| 129 | 143 | |
| 130 | 144 | /** |
| @@ -140,6 +154,21 @@ const stored = (page: string, theme: LookTheme): string => | ||
| 140 | 154 | const unstored = (page: string): string => |
| 141 | 155 | page.startsWith(DARK_PREFIX) ? page.slice(DARK_PREFIX.length) : page; |
| 142 | 156 | |
| 157 | const sizeOf = (r: Row): { size: LookSize } | undefined => | |
| 158 | r.scroll_width === null || | |
| 159 | r.scroll_height === null || | |
| 160 | r.inner_width === null || | |
| 161 | r.inner_height === null | |
| 162 | ? undefined | |
| 163 | : { | |
| 164 | size: { | |
| 165 | scrollWidth: r.scroll_width, | |
| 166 | scrollHeight: r.scroll_height, | |
| 167 | innerWidth: r.inner_width, | |
| 168 | innerHeight: r.inner_height, | |
| 169 | }, | |
| 170 | }; | |
| 171 | ||
| 143 | 172 | const toLook = (r: Row): Look => ({ |
| 144 | 173 | id: r.id, |
| 145 | 174 | appId: r.app_id, |
| @@ -154,6 +183,7 @@ const toLook = (r: Row): Look => ({ | ||
| 154 | 183 | takenAs: r.taken_as, |
| 155 | 184 | attachedBy: r.attached_by, |
| 156 | 185 | note: r.note, |
| 186 | ...sizeOf(r), | |
| 157 | 187 | }); |
| 158 | 188 | |
| 159 | 189 | export interface RecordLookInput { |
| @@ -170,6 +200,8 @@ export interface RecordLookInput { | ||
| 170 | 200 | /** A person's attachment: who, and what it shows. */ |
| 171 | 201 | readonly attachedBy?: string | undefined; |
| 172 | 202 | readonly note?: string | undefined; |
| 203 | /** What the page reported of its size; none stores nulls. */ | |
| 204 | readonly size?: LookSize | undefined; | |
| 173 | 205 | } |
| 174 | 206 | |
| 175 | 207 | /** Record a picture, or the reason there is none. A retake replaces. */ |
| @@ -177,10 +209,13 @@ export function recordLook(db: Database, input: RecordLookInput): Look { | ||
| 177 | 209 | const page = input.page ?? DEFAULT_PAGE; |
| 178 | 210 | const theme = input.theme ?? DEFAULT_THEME; |
| 179 | 211 | db.query( |
| 180 | `INSERT INTO look (id, app_id, env, commit_oid, page, kind, path, error, taken_as, attached_by, note, theme) | |
| 181 | VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12) | |
| 212 | `INSERT INTO look (id, app_id, env, commit_oid, page, kind, path, error, taken_as, attached_by, note, theme, | |
| 213 | scroll_width, scroll_height, inner_width, inner_height) | |
| 214 | VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16) | |
| 182 | 215 | ON CONFLICT (app_id, env, commit_oid, page, kind) DO UPDATE SET |
| 183 | 216 | path = excluded.path, error = excluded.error, taken_as = excluded.taken_as, |
| 217 | scroll_width = excluded.scroll_width, scroll_height = excluded.scroll_height, | |
| 218 | inner_width = excluded.inner_width, inner_height = excluded.inner_height, | |
| 184 | 219 | created_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')`, |
| 185 | 220 | ).run( |
| 186 | 221 | newId(), |
| @@ -195,6 +230,10 @@ export function recordLook(db: Database, input: RecordLookInput): Look { | ||
| 195 | 230 | input.attachedBy ?? null, |
| 196 | 231 | input.note ?? null, |
| 197 | 232 | theme, |
| 233 | input.size?.scrollWidth ?? null, | |
| 234 | input.size?.scrollHeight ?? null, | |
| 235 | input.size?.innerWidth ?? null, | |
| 236 | input.size?.innerHeight ?? null, | |
| 198 | 237 | ); |
| 199 | 238 | return getLook(db, input.appId, input.env, input.commitOid, page, input.kind, theme)!; |
| 200 | 239 | } |
src/core/migration-pins/20261109180000-a-look-keeps-its-page-size.pin added +1
| @@ -0,0 +1,1 @@ | ||
| 1 | 3019cfe53c7084e72513fa38efece1d492752c1129e6717962db12a6e7f345dd |
src/core/migrations/20261109180000-a-look-keeps-its-page-size.ts added +10
| @@ -0,0 +1,10 @@ | ||
| 1 | // A look keeps the size its page reported (#10202): the document's scroll | |
| 2 | // size and the window's inner size, so "does this page scroll sideways" is | |
| 3 | // answered from the row. Nullable, so every look from before reads as it | |
| 4 | // was: no size. Additive. | |
| 5 | export default ` | |
| 6 | ALTER TABLE look ADD COLUMN scroll_width INTEGER; | |
| 7 | ALTER TABLE look ADD COLUMN scroll_height INTEGER; | |
| 8 | ALTER TABLE look ADD COLUMN inner_width INTEGER; | |
| 9 | ALTER TABLE look ADD COLUMN inner_height INTEGER; | |
| 10 | `; |
src/looks/service.ts +1
| @@ -475,6 +475,7 @@ export function startLooks(app: App, options: LooksOptions): LooksHandle { | ||
| 475 | 475 | kind, |
| 476 | 476 | path, |
| 477 | 477 | takenAs: asNobody(kind, page) ? "nobody" : "owner", |
| 478 | ...(shot.ok && shot.value.size !== undefined ? { size: shot.value.size } : {}), | |
| 478 | 479 | }); |
| 479 | 480 | } else { |
| 480 | 481 | rmSync(staged, { force: true }); |
test/look-page-size.test.ts added +53
| @@ -0,0 +1,53 @@ | ||
| 1 | import { expect, test } from "bun:test"; | |
| 2 | import { Database } from "bun:sqlite"; | |
| 3 | import { getLook, recordLook } from "../src/core/looks.ts"; | |
| 4 | import { migrationFiles } from "../src/core/schema.ts"; | |
| 5 | ||
| 6 | const SIZE = { scrollWidth: 390, scrollHeight: 2000, innerWidth: 390, innerHeight: 844 }; | |
| 7 | const base = { appId: "a", env: "main", commitOid: "c1", kind: "phone" as const, path: "p.png" }; | |
| 8 | ||
| 9 | const fresh = (upTo?: string): Database => { | |
| 10 | const db = new Database(":memory:"); | |
| 11 | for (const m of migrationFiles) { | |
| 12 | if (upTo !== undefined && m.name >= upTo) break; | |
| 13 | db.exec(m.sql); | |
| 14 | } | |
| 15 | return db; | |
| 16 | }; | |
| 17 | ||
| 18 | test("a look keeps the size it was recorded with", () => { | |
| 19 | const db = fresh(); | |
| 20 | recordLook(db, { ...base, size: SIZE }); | |
| 21 | expect(getLook(db, "a", "main", "c1", "/", "phone", "light")?.size).toEqual(SIZE); | |
| 22 | }); | |
| 23 | ||
| 24 | test("a look without a size stores nulls and reads back with no size", () => { | |
| 25 | const db = fresh(); | |
| 26 | const look = recordLook(db, base); | |
| 27 | expect(look.size).toBeUndefined(); | |
| 28 | expect("size" in look).toBe(false); | |
| 29 | }); | |
| 30 | ||
| 31 | test("a retake without a size clears the old one", () => { | |
| 32 | const db = fresh(); | |
| 33 | recordLook(db, { ...base, size: SIZE }); | |
| 34 | expect(recordLook(db, base).size).toBeUndefined(); | |
| 35 | }); | |
| 36 | ||
| 37 | test("a row from before the migration reads with no size", () => { | |
| 38 | const db = fresh("20261109180000"); | |
| 39 | db.run( | |
| 40 | `INSERT INTO look (id, app_id, env, commit_oid, page, kind, path) VALUES ('x','a','main','c1','/','phone','p.png')`, | |
| 41 | ); | |
| 42 | db.exec(migrationFiles.find((m) => m.name.startsWith("20261109180000"))!.sql); | |
| 43 | const look = getLook(db, "a", "main", "c1", "/", "phone", "light"); | |
| 44 | expect(look?.path).toBe("p.png"); | |
| 45 | expect(look?.size).toBeUndefined(); | |
| 46 | }); | |
| 47 | ||
| 48 | test("a row with only some columns set has no size", () => { | |
| 49 | const db = fresh(); | |
| 50 | recordLook(db, base); | |
| 51 | db.run("UPDATE look SET scroll_width = 1"); | |
| 52 | expect(getLook(db, "a", "main", "c1", "/", "phone", "light")?.size).toBeUndefined(); | |
| 53 | }); |