Expose shape-only previews to native sheet libraries
Reviewed by GPT-6 Astra. Preserve sheet access checks and omit all cell contents from native preview metadata.
2 files changed,
+32
−1
internal/sheets/handlers_sheets.go+11 −1internal/sheets/native_test.go+21 −0
CI passed — run details
diff --git a/internal/sheets/handlers_sheets.go b/internal/sheets/handlers_sheets.go| index ad5ad1b..b96ff90 100644 |
| --- a/internal/sheets/handlers_sheets.go |
| +++ b/internal/sheets/handlers_sheets.go |
| @@ -101,7 +101,17 @@ func (a *App) renderSheetList(w http.ResponseWriter, r *http.Request, status int |
| return |
| } |
| if wantsNative(r) { |
| - nativeJSON(w, status, map[string]any{"Sheets": list}) |
| + // Reuse the web preview's shape-only query; never decrypt cells or |
| + // return HTML to a native renderer. |
| + type nativeSheetRow struct { |
| + SheetSummary |
| + Preview preview |
| + } |
| + rows := make([]nativeSheetRow, 0, len(list)) |
| + for _, sheet := range list { |
| + rows = append(rows, nativeSheetRow{SheetSummary: sheet, Preview: a.buildPreview(sheet.TabID)}) |
| + } |
| + nativeJSON(w, status, map[string]any{"Sheets": rows}) |
| return |
| } |
| rows := make([]sheetRow, 0, len(list)) |
diff --git a/internal/sheets/native_test.go b/internal/sheets/native_test.go| index 5629478..298b440 100644 |
| --- a/internal/sheets/native_test.go |
| +++ b/internal/sheets/native_test.go |
| @@ -33,6 +33,27 @@ func TestNativeSheetPayloadUsesExistingPermissions(t *testing.T) { |
| t.Fatal("anonymous native sheet read succeeded") |
| } |
| } |
| +func TestNativeListPreviewContainsShapeWithoutCellContents(t *testing.T) { |
| + a, cookies := signIn(t) |
| + _, cells := newSheet(t, a, cookies, "Workbook") |
| + secret := "private-cell-preview-sentinel" |
| + postCell(t, a, cookies, cells, `{"r":0,"c":0,"raw":"`+secret+`"}`) |
| + rec := do(t, a, as(nativeGet("/sheets"), cookies)) |
| + if rec.Code != 200 || strings.Contains(rec.Body.String(), secret) { |
| + t.Fatal("native preview leaked a cell or failed") |
| + } |
| + var payload struct{ Sheets []struct{ Preview preview } } |
| + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { |
| + t.Fatal(err) |
| + } |
| + if len(payload.Sheets) != 1 || payload.Sheets[0].Preview.Empty || len(payload.Sheets[0].Preview.Cells) != 1 { |
| + t.Fatal("native preview did not include the real cell shape") |
| + } |
| + if rec := do(t, a, nativeGet("/sheets")); rec.Code == 200 { |
| + t.Fatal("unsigned list exposed previews") |
| + } |
| +} |
| + |
| func TestNativeCreateAndHistoryCommandsAnswerJSON(t *testing.T) { |
| a, cookies := signIn(t) |
| r := postForm("/sheets", map[string][]string{"title": {"Created natively"}}) |