Skip to content

Composer frontend: /dashboards report builder UI#3

Merged
akim136 merged 3 commits into
feat/dashboardsfrom
feat/dashboards-ui
Jun 11, 2026
Merged

Composer frontend: /dashboards report builder UI#3
akim136 merged 3 commits into
feat/dashboardsfrom
feat/dashboards-ui

Conversation

@akim136

@akim136 akim136 commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Part B of the customizable reports/dashboards composer (the UI). Stacked on #2 (backend).

What

A /dashboards surface to compose a page of widgets pulling from any table.

  • /d (list + create) and /d/[dashboardId] (render), plus a Dashboards entry in the sidebar.
  • DashboardRenderer — add / edit / remove / reorder widgets (each PATCHes the dashboard config and refreshes).
  • DashboardWidgetKPI number · line · bar · small table.
  • WidgetBuilder modal — type → table → measure (count/sum/avg/min/max) → metric → group-by → date bucket → title. Reuses MetricChart; adds BarMini (recharts).
  • Per-widget data is computed server-side (computeWidgets) so the API secret stays off the client; client components mutate only via the BFF.
  • A demo "Sales pipeline" dashboard seeded over the CRM (pipeline KPIs + deals-by-stage bar + amount-by-month line + a deals table).

Quality

  • Security/correctness review: no high/med findings — aggregation SQL is allowlist + assertIdent + bound (no injection), all rendering goes through React (no XSS), secret stays server-side, per-widget fault isolation. Applied the two low-severity fixes: bounded the aggregate filter param (shared assertFilterBounds) and validated widget filter field refs.
  • 36 tests pass, apps/web builds, typecheck clean.

Includes two small backend hardening edits to the aggregate endpoint (from the review); the rest of the backend is in #2.

🤖 Generated with Claude Code

akim136 and others added 2 commits June 10, 2026 07:20
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 (#4)

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>
@akim136 akim136 merged commit f566535 into feat/dashboards Jun 11, 2026
akim136 added a commit that referenced this pull request Jun 11, 2026
* feat(api): aggregation endpoint + dashboards persistence (composer Part 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>

* Composer frontend: /dashboards report builder UI (#3)

* feat(web): reports/dashboards composer UI (composer Part B)

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>

* fix: review findings on the composer (3-branch review pass)

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>

* feat: linked-field sort/filter, nested filter groups, kanban field config, branding (#4)

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>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
akim136 added a commit that referenced this pull request Jun 11, 2026
* Port the dashboard view + public-repo polish + upstream sync workflow

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>

* Composer backend: aggregation API + dashboards persistence (#2)

* feat(api): aggregation endpoint + dashboards persistence (composer Part 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>

* Composer frontend: /dashboards report builder UI (#3)

* feat(web): reports/dashboards composer UI (composer Part B)

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>

* fix: review findings on the composer (3-branch review pass)

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>

* feat: linked-field sort/filter, nested filter groups, kanban field config, branding (#4)

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>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
akim136 added a commit that referenced this pull request Jun 12, 2026
… 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>
akim136 added a commit that referenced this pull request Jun 12, 2026
…f-service fields, rollups, hardening (#5)

* Port the dashboard view + public-repo polish + upstream sync workflow

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>

* feat(api): aggregation endpoint + dashboards persistence (composer Part 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>

* feat(web): reports/dashboards composer UI (composer Part B)

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>

* fix: review findings on the composer (3-branch review pass)

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>

* feat: linked-field sort/filter, nested filter groups, kanban field config, 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>

* feat(grid): field DnD reorder, load-more, bulk delete, kanban config, 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>

* feat(grid): bigger left drag handle + custom Kanban column order

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>

* feat(grid): self-service add/delete fields (incl. rollups) from the Fields 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>

* feat(grid): Calendar date-field config panel (mirrors Kanban setup)

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>

* feat: Gallery + Gantt view types with config panels (synced from upstream)

- 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>

* fix: review + security findings on dashboards/gallery/gantt (synced from 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>

* refactor: simplify-pass cleanups (synced from upstream)

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>

* feat: column resize, mobile layout fixes, full-set layout views (synced 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>

* feat: grouped rows, column header menu, kanban quick-add + collapse, 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>

* feat: kanban card ordering, keyboard nav + clipboard, summary footer, 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>

* docs: README — full feature overview (views, grid/kanban power features, dashboards, rollups, mobile)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant