Airtable feature parity: Gallery/Gantt views, view config panels, self-service fields, rollups, hardening#5
Merged
Merged
Conversation
Dashboard view type (recharts), synced from upstream as the first real upstream→OSS port: - lib/dashboard.ts (+test), DashboardView + MetricChart, ViewSwitcher entry, app dispatch, packages/api types + VIEW_TYPES allowlist, recharts dep, a demo "Trends" dashboard on Deals. 27 tests pass; apps/web builds. Public-repo polish: - LICENSE owner filled in; CI workflow (typecheck/test/build); SECURITY.md; issue + PR templates; README badges. Sync workflow: - scripts/sync-from-upstream.mjs — opt-in, dry-run-by-default port of the generic shared surface; OSS-specific files denied; a STRUCTURAL scrub gate (id shapes, no literal private values) plus an optional gitignored .sync-scrub for exact project strings. docs/SYNC.md documents it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rt A) Backend for a workspace-level reports/dashboards composer. - GET /v1/tables/:id/aggregate — grouped aggregation (count/sum/avg/min/max, optional date bucket day/week/month/year, filter). aggregate.ts builds the SQL from fixed allowlists (agg fn, bucket format) + registry-validated columns (assertIdent) + bound filter values — no user input interpolated. 9 tests. - meta_dashboards table (0004) — workspace-level (NOT table-bound); dashboards.ts CRUD (POST/PATCH/DELETE /v1/dashboards) validates every widget's table/field refs against the registry. loadDashboards in the registry; /v1/meta exposes them. - Verified on local D1 with the demo CRM: sum(amount) by stage + by month(close_date) return correct numbers; migration applies; 36 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A /dashboards surface to compose a page of widgets from any table. - Routes: /d (list + create), /d/[dashboardId] (render). Sidebar "Dashboards" nav. - DashboardRenderer (add/edit/remove/reorder widgets → PATCH config + refresh); DashboardWidget (KPI number · line · bar · small table); WidgetBuilder modal (type → table → measure/metric → group-by → date bucket → title); BarMini (recharts), reuses MetricChart. Per-widget data computed server-side (computeWidgets) so the API secret stays off the client; client mutates via BFF. - A demo "Sales pipeline" dashboard seeded over the CRM (KPIs + by-stage bar + amount-by-month line + deals table). - Review hardening (no high/med findings): aggregate filter now bounded (shared assertFilterBounds), widget filter field refs validated. 36 tests pass; apps/web builds; typecheck clean. Stacked on #2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From running code-review + security-review + simplify across the branches: - seed.mjs: rename the dashboard-block `DEALS` const (it shadowed the demo-rows `DEALS` array → SyntaxError that broke `node scripts/seed.mjs` entirely). - KPI correctness: computeWidgets never groups a KPI (ignores any stale groupBy/bucket), so a KPI always shows the grand total, not the first group's value; WidgetBuilder also clears groupBy/bucket when switching to KPI/table. - dashboards validateConfig: fail fast on un-runnable widgets (sum/avg/min/max without a metric; line/bar without a group-by) instead of erroring at query time; cap widgets per dashboard (MAX_WIDGETS=50) to bound the per-render query fan-out. Security review: no exploitable issues; public-repo leak check clean (only the owner's public handle appears). 36 tests pass; build + typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nfig, branding
Port of the four grid feature gaps into gridbase (packages/api + apps/web),
mirroring search-system's grid implementation.
A) Sort/filter by a linked record's sub-field
- types.ts: FilterCondition + sort entries gain optional linkedFieldId; lookups
traverse their via->target automatically.
- filter.ts: new resolveLinkTarget/compare/linkedFieldClause; buildWhere emits a
correlated EXISTS joining the link join-table to the linked table; buildOrderBy
emits a correlated scalar subquery (LIMIT 1). Both now take the Registry.
- repo.ts + aggregate.ts: thread reg into buildWhere/buildOrderBy.
- index.ts parseListOpts: parse "fieldId>linkedFieldId:dir" sort token.
- apps/web: lib/types.ts mirror; gridApi.ts serializes the linked sort token;
ViewToolbar filter/sort UI gains a linked sub-field dropdown (default = primary);
link/lookup fields are no longer excluded from filter/sort dropdowns.
B) Kanban card field config
- KanbanView: card fields now follow the view FieldEditor (visibleFields) minus the
stack + primary; removed the hardcoded link exclusion and slice(0,3); added
view.config.kanban.maxPreviewFields (default 8); link/lookup arrays render via labels.
C) Nested AND/OR filter (Airtable-style, one level)
- types.ts: FilterSpec.conditions is Array<FilterCondition | FilterGroup> with an
isFilterGroup guard; old flat specs stay valid.
- filter.ts buildWhere: groups recurse one level, parenthesized, joined by the
group conjunction.
- index.ts assertFilterBounds: cap leaf conditions + group count + depth=1.
- dashboards.ts validateConfig: flatten one group level to validate leaf fields.
- ViewToolbar FilterEditor: Add condition / Add group; group conjunction toggle.
D) Branding
- app/icon.png + public/grid.png (from Downloads/grid.png); layout metadata title/
description/icon; Sidebar logo (next/image) in expanded + collapsed states; app
name kept as "gridbase".
Tests: packages/api filter.test.ts extended (19 passing) — nested-group WHERE,
linked-field WHERE EXISTS, linked-field ORDER BY subquery, lookup traversal.
@gridbase/api test (28 passing) + typecheck clean; @gridbase/web typecheck + next
build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… rollup fields Mirror of the grid UX + rollup work (built & verified in search-system/apps/grid first): - #1 Field reorder: drag-and-drop (@dnd-kit/sortable) in FieldEditor. - #2 Pagination: client TableView appends pages via offset cursor (loadMoreRecords). - #3 Bulk delete: checkbox column + select-all + bulk Delete; chunked deleteRecords. - #4 Kanban config: Kanban toolbar panel to set the stack field; columns from choices ∪ present values. - #5 Rollup field type: COUNT/SUM/AVG/MIN/MAX across a link (resolveRollups), read-only; IN(...) chunked under D1's 100-param cap. Example rollups added to the demo schema. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror of the grid handle + Kanban column-order changes: - Field reorder handle on the left, enlarged, touch-none, scrollbar-clear. - Kanban config gains a drag-to-reorder "Column order" list (config.kanban.columnOrder); KanbanView honors it, Uncategorized last. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ields menu Mirror of the field self-service feature: - packages/api: POST/DELETE /v1/tables/:id/fields (fields.ts). Computed fields insert a meta row; stored fields ALTER TABLE ADD COLUMN. Delete guards the primary field + referenced fields and DROP COLUMNs stored ones. Schema cache invalidated. - UI: "+ Add field" form (with a rollup builder) + hover-✕ delete in the Fields menu; new fields auto-show in the current view. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror: a Calendar toolbar panel to pick the date field (config.calendar.dateFieldId), paralleling the Kanban stack-field picker, replacing the "no date field configured" dead-end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ream) - GalleryView: responsive card grid (primary-field title, Fields-editor card fields, optional url-field cover, maxPreviewFields cap) + Gallery toolbar panel. - GanttView: per-record timeline bars from start/end date fields with month ticks + Gantt toolbar panel (start/end pickers). - Registered in ViewSwitcher, view page, ViewConfig types, VIEW_TYPES allowlist; previewValue extracted to lib/preview (shared by Kanban + Gallery). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rom upstream) - WidgetBuilder materializes the bucket default + clears fieldIds/filter on table change; DashboardRenderer disables controls during in-flight saves. - validateConfig: widgetId required+unique, numeric stored metric for non-count aggs, stored group-by for charts, numeric position; PATCH rejects malformed JSON. - loadDashboards tolerates a missing meta_dashboards table (deploy-before-migrate). - createField pins rollup references: via on the field's table, target a number field on the linked table (a dangling reference 500'd every read of the table). - GanttView pins zone-less datetimes to UTC; single dated/undated pass; tick left% precomputed. Detail views regain overflow-hidden. - Cleanups: shared meta_dashboards row mapper; VIEW_TYPES/AGG_FNS const single sources; ViewToolbar config panels collapsed into a typeEditors map; CountInput commit-on-blur; DashboardWidget reuses previewValue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
recordTitle + FieldSelect shared helpers, WidgetResult single declaration, fieldsForTable in WidgetBuilder/DashboardWidget (+ isComputed group-by filter that was missing rollup), ViewSwitcher TYPE_INFO registry keyed by ViewType, views/dashboards PATCH reject malformed JSON consistently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3256d25 to
6381371
Compare
…ed from upstream) - Drag-to-resize column widths persisted to config.fields[].width - h-dvh + viewport-fit=cover + safe-area padding; wrapping header/toolbar rows; viewport-clamped popovers - Layout views (kanban/calendar/gallery/gantt) page through the whole record set (bounded) instead of showing only the first 100 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mobile sidebar (synced) - Grouped rows via config.groupBy with collapsible section headers - Column header ▾ menu: sort asc/desc, group by, hide field - Kanban per-column quick-add + collapsible columns (config.kanban.collapsedColumns) - Mobile sidebar: icon-rail default + overlay expansion with backdrop Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… row colors, side peek (synced) - Sortable cards within kanban columns (config.kanban.cardOrder), cross-column drops slot into position - Cell cursor: arrows / Enter / Esc / ⌘C / ⌘V on the grid - Sticky summary footer (config.summaries) with count/sum/avg/min/max - Row tinting by a select field (config.colorBy) via the header menu - Record side-peek drawer with inline editing (⌘-click for full page) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…es, dashboards, rollups, mobile) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Syncs the full feature set from upstream development into gridbase:
New view types + config UX
Schema self-service
POST/DELETE /v1/tables/:id/fields; stored fields get an additiveALTER TABLE ADD COLUMN, computed fields are metadata-only; delete guards the primary field and referenced fieldsHardening (review + security passes)
loadDashboardstolerates a missingmeta_dashboardstable (deploy-before-migrate can't take every route down)VIEW_TYPES/AGG_FNSconsts,recordTitle/FieldSelect/previewValue/WidgetResult, unified meta_dashboards row mapperVerification
next buildgreen.dev.vars.example(no secrets) tracked🤖 Generated with Claude Code