Composer frontend: /dashboards report builder UI#3
Merged
Conversation
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
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>
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.
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).DashboardWidget— KPI number · line · bar · small table.WidgetBuildermodal — type → table → measure (count/sum/avg/min/max) → metric → group-by → date bucket → title. ReusesMetricChart; addsBarMini(recharts).computeWidgets) so the API secret stays off the client; client components mutate only via the BFF.Quality
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 aggregatefilterparam (sharedassertFilterBounds) and validated widget filter field refs.apps/webbuilds, typecheck clean.🤖 Generated with Claude Code