import { createMockFacade } from './facade/mockFacade' import { createSpreadsheet } from './facade/spreadsheet' type SpreadsheetReport = { schemaVersion: 1 status: 'pass' | 'failed' browserId: 'chrome' crossOriginIsolated: boolean cells?: { width: number; length: number; updatedLength: number; dimension: string } aliases?: string[] dependencies?: string[] cycle?: { detected: boolean; cells: string[] } binding?: { alias: string; objectId: string; propertyName: string; appliedValue: number; undoValue: number; redoValue: number } expression?: { value: number; dimension: string; references: string[] } layout?: { styledCells: number; namedRange: string; mergedRange: string; shiftedFormulaAddress: string } csv?: { rows: number; columns: number; bytes: number } largeSheet?: { cells: number; rows: number; columns: number; evaluationErrors: number; csvBytes: number; durationMs: number; budgetMs: number } persistence?: { mode: string; savedVersion: number; reopenedVersion: number; reopenedValue: number } resource?: { hash: string; byteLength: number; roundTrip: boolean; released: boolean } opfs?: { markerSuite: string; markerCells: number; markerRemoved: boolean } afterRelease?: { shapeCount: number; kernelReferenceCount: number } error?: string } const propertyValue = (facade: ReturnType, objectId: string, name: string) => facade.app.document.getObject(objectId)?.properties.find((property) => property.name === name)?.value const run = async (): Promise => { const facade = createMockFacade() const report: SpreadsheetReport = { schemaVersion: 1, status: 'failed', browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true } try { if (!report.crossOriginIsolated || !navigator.storage?.getDirectory) throw new Error('Chrome Spreadsheet evidence requires isolated OPFS.') await facade.project.list() const sheet = createSpreadsheet('spreadsheet', 'Parameters') sheet.setCell('A1', '6 mm', { alias: 'Width' }) sheet.setCell('A2', '=Width * 2', { alias: 'Length' }) sheet.setCell('B1', '=max(A1, 4 mm)') const binding = sheet.bindProperty('Length', 'pad', 'Length') const first = sheet.evaluate() const width = first.cells.A1.value const length = first.cells.A2.value if (!width || !length || first.errors.length > 0) throw new Error(`Spreadsheet formula evaluation failed: ${JSON.stringify(first.errors)}`) const expression = facade.app.expression.evaluate('Spreadsheet.Length + 1 mm', { 'Spreadsheet.Length': length }) facade.app.document.setProperty({ objectId: binding.objectId, propertyName: binding.propertyName, value: length.value }) sheet.setCell('A1', '8 mm', { alias: 'Width' }) const updated = sheet.evaluate() const updatedLength = updated.cells.A2.value if (!updatedLength) throw new Error('Spreadsheet alias update did not recompute Length.') facade.app.document.setProperty({ objectId: binding.objectId, propertyName: binding.propertyName, value: updatedLength.value }) const appliedValue = Number(propertyValue(facade, 'pad', 'Length')) facade.history.undo() const undoValue = Number(propertyValue(facade, 'pad', 'Length')) facade.history.redo() const redoValue = Number(propertyValue(facade, 'pad', 'Length')) sheet.setCell('C1', '=D1 + 1') sheet.setCell('D1', '=C1 + 1') const diagnosed = sheet.evaluate() const cycleCells = [...new Set(diagnosed.cycles.flat())].sort() const csv = sheet.exportCsv() const csvRows = csv.split('\n') const layout = createSpreadsheet('layout', 'Layout') layout.setCell('A1', 1) layout.setCell('A2', '=A1 + 1') const styledCells = layout.setStyle('A1:B2', { background: '#e6f4f1', bold: true, numberFormat: '0.00' }) layout.mergeCells('B1:A2') layout.setNamedRange('Inputs', 'A1:A2') layout.insertRows(2) layout.insertColumns(2) const layoutSnapshot = layout.snapshot() const largeStarted = performance.now() const large = createSpreadsheet('large', 'Large sheet') const columnName = (column: number) => { let value = ''; for (let current = column; current > 0; current = Math.floor((current - 1) / 26)) value = String.fromCharCode(65 + ((current - 1) % 26)) + value; return value } for (let row = 1; row <= 100; row += 1) for (let column = 1; column <= 100; column += 1) large.setCell(`${columnName(column)}${row}`, row + column) const largeEvaluation = large.evaluate() const largeCsv = large.exportCsv() const largeDurationMs = performance.now() - largeStarted const document = facade.app.document.getActive() const saved = await facade.project.save(document) const reopened = await facade.project.load(document.id) const reopenedValue = Number(reopened?.objects.find((object) => object.id === 'pad')?.properties.find((property) => property.name === 'Length')?.value) if (!reopened || reopenedValue !== updatedLength.value) throw new Error('Spreadsheet-bound property did not survive the OPFS project round-trip.') const payload = new TextEncoder().encode(JSON.stringify({ sheet: sheet.snapshot(), layout: layoutSnapshot })) const stored = await facade.project.resource.put(payload, 'application/vnd.bitbybit.spreadsheet+json') const resourceRoundTrip = await facade.project.resource.get(stored.hash) const roundTrip = resourceRoundTrip !== null && new TextDecoder().decode(resourceRoundTrip) === new TextDecoder().decode(payload) await facade.project.resource.release(stored.hash) const released = await facade.project.resource.get(stored.hash) === null const directory = await navigator.storage.getDirectory() const markerName = 'spreadsheet-chrome-marker.json' const marker = await directory.getFileHandle(markerName, { create: true }) const writable = await marker.createWritable() await writable.write(JSON.stringify({ suite: 'SS-ALL', cells: sheet.snapshot().cells.length + layoutSnapshot.cells.length })) await writable.close() const markerPayload = JSON.parse(await (await marker.getFile()).text()) as { suite: string; cells: number } await directory.removeEntry(markerName) let markerRemoved = false try { await directory.getFileHandle(markerName) } catch (error) { markerRemoved = error instanceof DOMException && error.name === 'NotFoundError' } report.cells = { width: width.value, length: length.value, updatedLength: updatedLength.value, dimension: updatedLength.dimension } report.aliases = sheet.snapshot().cells.flatMap((cell) => cell.alias ? [cell.alias] : []).sort() report.dependencies = first.dependencies.map((dependency) => `${dependency.source}->${dependency.target}:${dependency.reference}`).sort() report.cycle = { detected: diagnosed.cycles.length > 0, cells: cycleCells } report.binding = { ...binding, appliedValue, undoValue, redoValue } report.expression = { value: expression.value.value, dimension: expression.value.dimension, references: expression.references } report.layout = { styledCells: styledCells.length, namedRange: layoutSnapshot.namedRanges[0]?.range ?? '', mergedRange: layoutSnapshot.mergedRanges[0] ?? '', shiftedFormulaAddress: layoutSnapshot.cells.find((cell) => cell.input === '=A1 + 1')?.address ?? '' } report.csv = { rows: csvRows.length, columns: Math.max(...csvRows.map((row) => row.split(',').length)), bytes: new TextEncoder().encode(csv).byteLength } report.largeSheet = { cells: large.snapshot().cells.length, rows: 100, columns: 100, evaluationErrors: largeEvaluation.errors.length, csvBytes: new TextEncoder().encode(largeCsv).byteLength, durationMs: largeDurationMs, budgetMs: 5000 } report.persistence = { mode: facade.project.capabilities().mode, savedVersion: saved.documentVersion, reopenedVersion: reopened.version, reopenedValue } report.resource = { hash: stored.hash, byteLength: stored.byteLength, roundTrip, released } report.opfs = { markerSuite: markerPayload.suite, markerCells: markerPayload.cells, markerRemoved } report.status = report.cells.width === 6 && report.cells.length === 12 && report.cells.updatedLength === 16 && report.cells.dimension === 'length' && JSON.stringify(report.aliases) === JSON.stringify(['Length', 'Width']) && report.dependencies.length === 2 && report.cycle.detected && JSON.stringify(report.cycle.cells) === JSON.stringify(['C1', 'D1']) && report.binding.appliedValue === 16 && report.binding.undoValue === 12 && report.binding.redoValue === 16 && report.expression.value === 13 && report.expression.dimension === 'length' && report.expression.references.includes('Spreadsheet.Length') && report.layout.styledCells === 4 && report.layout.namedRange === 'A1:A3' && report.layout.mergedRange === 'A1:C3' && report.layout.shiftedFormulaAddress === 'A3' && report.largeSheet.cells === 10000 && report.largeSheet.evaluationErrors === 0 && report.largeSheet.csvBytes > 0 && report.largeSheet.durationMs <= report.largeSheet.budgetMs && report.persistence.mode === 'sqlite-opfs' && report.resource.roundTrip && report.resource.released && report.opfs.markerSuite === 'SS-ALL' && report.opfs.markerCells === 9 && report.opfs.markerRemoved ? 'pass' : 'failed' } catch (error) { report.error = error instanceof Error ? error.stack || error.message : String(error) } finally { facade.geometry.dispose() await new Promise((resolve) => setTimeout(resolve, 0)) report.afterRelease = { shapeCount: facade.geometry.capabilities().shapeCount, kernelReferenceCount: facade.geometry.capabilities().kernelReferenceCount } if (report.afterRelease.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) { report.status = 'failed'; report.error = `${report.error ? `${report.error} ` : ''}Shape ownership gate failed.` } } return report } run().then((report) => { ;(window as Window & { __bitbybitSpreadsheetReport?: SpreadsheetReport }).__bitbybitSpreadsheetReport = report document.documentElement.dataset.status = report.status document.querySelector('#result')!.textContent = JSON.stringify(report, null, 2) }).catch((error) => { document.documentElement.dataset.status = 'failed' document.querySelector('#result')!.textContent = error instanceof Error ? error.stack || error.message : String(error) }) export {}