Skip to content

feat(views): store a grid of widgets on grid views - #14

Open
remko48 wants to merge 5 commits into
stack/g1-views-without-tablefrom
stack/g2-grid-widget-types
Open

remko48 wants to merge 5 commits into
stack/g1-views-without-tablefrom
stack/g2-grid-widget-types

Conversation

@remko48

@remko48 remko48 commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Grid views get the content that makes them a page: a set of widgets and their position on a grid. Which widget types exist, and what each may contain, is defined on the server. Every saved grid is checked against those definitions, so the database only holds known widget types and properties.

What changes

  • Migration: tables_views gets a nullable grid text column (JSON).

  • Widget types (GridWidgetTypes): header, text and data (shows a table or view). Each type has a configuration schema (title, show title) and a properties schema for what the widget shows, stored as the widget's content. These definitions are not exposed through the API yet.

  • Only grid views hold a grid: sending a grid for a table view is rejected. A single request can change the type to grid and fill the grid at the same time.

  • Validation on save and import: the following are rejected with a 400:

    • unknown widget types
    • values of the wrong type
    • missing required values
    • two widgets with the same id, or a widget placed twice in the layout
    • layout items that point at a widget the grid does not have
    • a layout position below 0 or a size below 1

    Unknown properties are dropped and missing ones get their default.

  • Colors: only hex colors (# plus 3 to 6 hex digits) or an empty value are accepted, so no CSS can be injected through a widget.

  • Data widget: a new or changed target must exist, must not be a view without a table, and the user must be able to read its rows. Every failure gives the same error, so it reveals nothing about other people's views. A target that was already stored is kept as it is, so a save does not depend on the saver's access to widgets they did not touch. It is marked broken: true once its table or view no longer exists.

  • Import: only views of type grid take over a grid from the import, and it goes through the same validation.

  • API: grid (widgets + layout) is added to the view response and can be sent through the existing view update. OpenAPI and TS types regenerated.

🖼️ Screenshots

Backend only, no UI changes.

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔙 Backport requests are created or not needed: /backport to stableX.X
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

Grid views keep their widgets and layout in a new grid column. The widget
types (header, text and data) and the schemas of their configuration and
content are defined on the server, and every grid is validated against
them before it is stored.

- unknown properties are dropped and missing ones get their default
- colors must be hex values
- a data widget cannot point at another view without a table
- grids are validated on update and on import

Co-authored-by: Ruben van der Linde <ruben@conduction.nl>
Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Remko <remko@conduction.nl>
@remko48
remko48 added this pull request to stack #15 October 12, 2026 08:26
Comment thread lib/Db/View.php Outdated
Comment thread lib/Service/GridWidgetTypes.php Outdated
Comment thread lib/Service/ViewService.php
Comment thread lib/Service/GridWidgetTypes.php
Comment thread lib/ResponseDefinitions.php Outdated
Comment thread lib/Service/ViewService.php
- a data widget only shows a table or view that exists, holds rows and is
  readable for the user; every other target gets the same error
- only a grid view stores a grid, also when the type changes in the same
  request; an import only takes the grid over for a grid view
- a layout places every widget once and with a size of at least one cell
- a grid that cannot be encoded is refused instead of being stored empty
- widget values are typed in the API instead of mixed, and the shapes are
  defined once in ResponseDefinitions
- the conversion of a grid shape that only prototype data had is gone

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Remko <remko@conduction.nl>
Comment thread lib/Service/GridWidgetTypes.php Outdated
A save now only checks targets that are new or changed against the user who
saves. A target that was stored before is kept, so a save no longer depends
on the saver's access to widgets they did not touch, and a target that no
longer exists is marked broken instead of blocking every save; the mark
clears once the widget is saved in a valid state again.

Tests now cover the refusal of a view without a table on its own, and the
import of a table view whose source carries a grid.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Remko <remko@conduction.nl>
…n cases

A test now saves a grid through ViewService::update() with a stored target
the saver cannot read, so dropping the stored grid from that call fails it.
The input-rejection cases of GridWidgetTypesTest run as one data-provider
test, each with its own name and expected message.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Remko <remko@conduction.nl>
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.

2 participants