Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 22 additions & 24 deletions skills/objectstack-ui/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,7 @@ App navigation, Dashboards, Reports, and Actions.
| `map` | Geospatial records with `location` fields |
| `chart` | Aggregate visualisation over the object (mini chart view) |
| `tree` | Self-referencing hierarchy (tree-grid) |
| `page` | Mounts a published Page (`pageName`); no rows of its own |

### Form Views

Expand DownExpand Up@@ -322,11 +323,11 @@ Rules:
Want both demos? Put them on different views.
- **On an object list view (`*.view.ts` `list` / `listViews`), only
`element: 'dropdown'` (value chips) is allowed — `tabs` is page-only**
(ADR-0047 amendment). An object view's
saved-view `ViewTabBar` already owns the tab-bar role, so a `tabs`
user-filter would render a second, colliding tab bar. The spec narrows it
(`ObjectUserFiltersSchema` — a `tabs` element is untypable at author time
and dropped at parse) and the `validate` list-view-mode lint reports it.
(ADR-0047 amendment). An object view's saved-view `ViewTabBar` already owns
the tab-bar role, so a `tabs` user-filter would render a second, colliding
tab bar. The spec narrows it (`ObjectUserFiltersSchema` — a `tabs` element is
untypable at author time and **rejected** at parse, not dropped) and the
`validate` list-view-mode lint reports it.
Need named presets on an object? Add a `listViews` entry instead. The full
`dropdown | tabs | toggle` range applies only to **page lists** /
`interfaceConfig.userFilters` (the block above).
Expand DownExpand Up@@ -558,7 +559,7 @@ Field shapes:
| `bands[].label` | yes | Header text for the band (白班 / 夜班). |
| `bands[].start` / `bands[].end` | yes | `'HH:mm'`. When `end <= start` the band crosses midnight. Bands must tile the 24h day from `dayStart`. |
| `bands[].color` | no | Any CSS color. Tints that band's column; **omit for no tint**. |
| `showMidnight` | no | Draw the dashed calendar-midnight cue inside cross-midnight bands. Default `true`; set `false` to hide it. |
| `showMidnight` | no | Draw the dashed local-0:00 cue inside cross-midnight bands (the 排班日 cell itself stays unbroken). Default `true`; set `false` to hide it. |

Behavior:

Expand All@@ -570,11 +571,7 @@ Behavior:
in, so a 夜班 spanning 20:00→次日08:00 stays in a single column.
- **Drag-snaps to band boundaries** (the band duration, e.g. 12h) instead of
whole days.
- **Calendar-midnight cue.** A subtle dashed vertical line marks local 0:00
*inside* a cross-midnight band — the 排班日 cell itself stays unbroken. Set
`showMidnight: false` to turn it off.
- **Default off = zero regression.** Omit `timeSegments` and the gantt behaves
exactly as before. Tints render only for bands that declare `color`.
- **Default off.** Omit `timeSegments` and the gantt renders unsegmented.

---

Expand DownExpand Up@@ -669,6 +666,7 @@ A widget's `type` is its **chart type** (`ChartTypeSchema`; defaults to
| Single value | `metric`, `kpi`, `gauge`, `solid-gauge`, `bullet` (all render the number today; gauge variants gain a dial when a gauge renderer lands) |
| Comparison | `bar`, `horizontal-bar`, `column` |
| Trend | `line`, `area` |
| Mixed | `combo` (bar/line/area on shared dual axes) |
| Distribution | `pie`, `donut`, `funnel` |
| Relationship | `scatter` |
| Composition | `treemap`, `sankey` |
Expand All@@ -688,9 +686,9 @@ dashboard widget, a report, and a list `type:'chart'` view all **bind a `dataset
and select named `dimensions` + `values`**; the dataset owns the base object,
allowed joins, intrinsic filter, dimensions, and certified measures. The legacy
per-widget inline query (`object` + `categoryField` + `valueField` + `aggregate`)
**was removed** — a widget now requires `dataset` + `values`; the inline fields are
dropped and a widget lacking `dataset` fails `os validate`. Reports bind the same
way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
**was removed** — a widget now requires `dataset` + `values`; the closed schema
REJECTS the inline keys by name, and one lacking `dataset` fails `os validate`.
Reports bind the same way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
`DatasetSchema` — see `node_modules/@objectstack/spec/src/ui/dataset.zod.ts`.

A widget's presentation-scope `filter` flows into the query as the runtime
Expand DownExpand Up@@ -1523,9 +1521,9 @@ compareTo: { kind: 'previousYear', dimension: 'close_date' } // several —
and overwrites it.
* **Cartesian charts** (`line` / `area` / `bar` / `horizontal-bar` /
`scatter`) — the comparison series is appended after the primary series
with `variant: 'comparison'` and styled as a muted overlay (`opacity: 0.5`
+ `strokeDasharray: '4 4'` for line/area/scatter; `opacity: 0.4` for
bars). Override per-series with `series.dashArray` / `series.opacity`.
with `variant: 'comparison'`, muted per family (dashed `'4 4'` on
line/area only; reduced opacity on all). Override per-series with
`series.dashArray` / `series.opacity`.
* **Pie / donut / funnel** — `compareTo` is silently ignored; there is no
meaningful "two-period" composition for part-of-whole charts.
* **Requirements** — a comparison needs a **dated window** to shift. When the
Expand DownExpand Up@@ -1663,12 +1661,12 @@ Both `{token}` and `${token}` forms are accepted.
| Instants | `today`, `yesterday`, `tomorrow`, `now` |
| Current period | `current_week_start` / `_end`, `current_month_start` / `_end`, `current_quarter_start` / `_end`, `current_year_start` / `_end` |
| Last period | `last_week_start` / `_end`, `last_month_start` / `_end`, `last_quarter_start` / `_end`, `last_year_start` / `_end` |
| Next period | `next_week_start`, `next_month_start`, `next_quarter_start`, `next_year_start` |
| Next period | `next_week_start` / `_end`, `next_month_start` / `_end`, `next_quarter_start` / `_end`, `next_year_start` / `_end` |
| Bare aliases | `week_start`, `week_end`, `month_start`, `month_end`, `quarter_start`, `quarter_end`, `year_start`, `year_end` (same as `current_*`) |

### Parameterised tokens — `{N_<unit>_(ago|from_now)}`

`N` is any positive integer; `<unit>` is one of
`N` is any non-negative integer; `<unit>` is one of
`minute(s) | hour(s) | day(s) | week(s) | month(s) | year(s)`.
`minute`/`hour` resolve to a full ISO timestamp; coarser units resolve to
`YYYY-MM-DD`.
Expand DownExpand Up@@ -2053,11 +2051,11 @@ the selected row in `list_item` contexts.
"My Records" filter using `$currentUser`.

5. **Putting widget grid placement in `position`.**
The grid-placement field is `layout: { x, y, w, h }` — there is no
`position` key on a widget, so a `position` object is silently dropped.
`layout` is optional: omit it and the widget auto-flows (the Studio
designer relies on this); set it only when you want an explicit grid
position.
The grid-placement field is `layout: { x, y, w, h }` — `position` is not a
widget key and the closed schema REJECTS it by name (it was silently
dropped before protocol 17). `layout` is optional: omit it and the widget
auto-flows (the Studio designer relies on this); set it only when you want
an explicit grid position.

---

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 22 additions & 24 deletions skills/objectstack-ui/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,7 @@ App navigation, Dashboards, Reports, and Actions.
| `map` | Geospatial records with `location` fields |
| `chart` | Aggregate visualisation over the object (mini chart view) |
| `tree` | Self-referencing hierarchy (tree-grid) |
| `page` | Mounts a published Page (`pageName`); no rows of its own |

### Form Views

Expand DownExpand Up@@ -322,11 +323,11 @@ Rules:
Want both demos? Put them on different views.
- **On an object list view (`*.view.ts` `list` / `listViews`), only
`element: 'dropdown'` (value chips) is allowed — `tabs` is page-only**
(ADR-0047 amendment). An object view's
saved-view `ViewTabBar` already owns the tab-bar role, so a `tabs`
user-filter would render a second, colliding tab bar. The spec narrows it
(`ObjectUserFiltersSchema` — a `tabs` element is untypable at author time
and dropped at parse) and the `validate` list-view-mode lint reports it.
(ADR-0047 amendment). An object view's saved-view `ViewTabBar` already owns
the tab-bar role, so a `tabs` user-filter would render a second, colliding
tab bar. The spec narrows it (`ObjectUserFiltersSchema` — a `tabs` element is
untypable at author time and **rejected** at parse, not dropped) and the
`validate` list-view-mode lint reports it.
Need named presets on an object? Add a `listViews` entry instead. The full
`dropdown | tabs | toggle` range applies only to **page lists** /
`interfaceConfig.userFilters` (the block above).
Expand DownExpand Up@@ -558,7 +559,7 @@ Field shapes:
| `bands[].label` | yes | Header text for the band (白班 / 夜班). |
| `bands[].start` / `bands[].end` | yes | `'HH:mm'`. When `end <= start` the band crosses midnight. Bands must tile the 24h day from `dayStart`. |
| `bands[].color` | no | Any CSS color. Tints that band's column; **omit for no tint**. |
| `showMidnight` | no | Draw the dashed calendar-midnight cue inside cross-midnight bands. Default `true`; set `false` to hide it. |
| `showMidnight` | no | Draw the dashed local-0:00 cue inside cross-midnight bands (the 排班日 cell itself stays unbroken). Default `true`; set `false` to hide it. |

Behavior:

Expand All@@ -570,11 +571,7 @@ Behavior:
in, so a 夜班 spanning 20:00→次日08:00 stays in a single column.
- **Drag-snaps to band boundaries** (the band duration, e.g. 12h) instead of
whole days.
- **Calendar-midnight cue.** A subtle dashed vertical line marks local 0:00
*inside* a cross-midnight band — the 排班日 cell itself stays unbroken. Set
`showMidnight: false` to turn it off.
- **Default off = zero regression.** Omit `timeSegments` and the gantt behaves
exactly as before. Tints render only for bands that declare `color`.
- **Default off.** Omit `timeSegments` and the gantt renders unsegmented.

---

Expand DownExpand Up@@ -669,6 +666,7 @@ A widget's `type` is its **chart type** (`ChartTypeSchema`; defaults to
| Single value | `metric`, `kpi`, `gauge`, `solid-gauge`, `bullet` (all render the number today; gauge variants gain a dial when a gauge renderer lands) |
| Comparison | `bar`, `horizontal-bar`, `column` |
| Trend | `line`, `area` |
| Mixed | `combo` (bar/line/area on shared dual axes) |
| Distribution | `pie`, `donut`, `funnel` |
| Relationship | `scatter` |
| Composition | `treemap`, `sankey` |
Expand All@@ -688,9 +686,9 @@ dashboard widget, a report, and a list `type:'chart'` view all **bind a `dataset
and select named `dimensions` + `values`**; the dataset owns the base object,
allowed joins, intrinsic filter, dimensions, and certified measures. The legacy
per-widget inline query (`object` + `categoryField` + `valueField` + `aggregate`)
**was removed** — a widget now requires `dataset` + `values`; the inline fields are
dropped and a widget lacking `dataset` fails `os validate`. Reports bind the same
way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
**was removed** — a widget now requires `dataset` + `values`; the closed schema
REJECTS the inline keys by name, and one lacking `dataset` fails `os validate`.
Reports bind the same way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
`DatasetSchema` — see `node_modules/@objectstack/spec/src/ui/dataset.zod.ts`.

A widget's presentation-scope `filter` flows into the query as the runtime
Expand DownExpand Up@@ -1523,9 +1521,9 @@ compareTo: { kind: 'previousYear', dimension: 'close_date' } // several —
and overwrites it.
* **Cartesian charts** (`line` / `area` / `bar` / `horizontal-bar` /
`scatter`) — the comparison series is appended after the primary series
with `variant: 'comparison'` and styled as a muted overlay (`opacity: 0.5`
+ `strokeDasharray: '4 4'` for line/area/scatter; `opacity: 0.4` for
bars). Override per-series with `series.dashArray` / `series.opacity`.
with `variant: 'comparison'`, muted per family (dashed `'4 4'` on
line/area only; reduced opacity on all). Override per-series with
`series.dashArray` / `series.opacity`.
* **Pie / donut / funnel** — `compareTo` is silently ignored; there is no
meaningful "two-period" composition for part-of-whole charts.
* **Requirements** — a comparison needs a **dated window** to shift. When the
Expand DownExpand Up@@ -1663,12 +1661,12 @@ Both `{token}` and `${token}` forms are accepted.
| Instants | `today`, `yesterday`, `tomorrow`, `now` |
| Current period | `current_week_start` / `_end`, `current_month_start` / `_end`, `current_quarter_start` / `_end`, `current_year_start` / `_end` |
| Last period | `last_week_start` / `_end`, `last_month_start` / `_end`, `last_quarter_start` / `_end`, `last_year_start` / `_end` |
| Next period | `next_week_start`, `next_month_start`, `next_quarter_start`, `next_year_start` |
| Next period | `next_week_start` / `_end`, `next_month_start` / `_end`, `next_quarter_start` / `_end`, `next_year_start` / `_end` |
| Bare aliases | `week_start`, `week_end`, `month_start`, `month_end`, `quarter_start`, `quarter_end`, `year_start`, `year_end` (same as `current_*`) |

### Parameterised tokens — `{N_<unit>_(ago|from_now)}`

`N` is any positive integer; `<unit>` is one of
`N` is any non-negative integer; `<unit>` is one of
`minute(s) | hour(s) | day(s) | week(s) | month(s) | year(s)`.
`minute`/`hour` resolve to a full ISO timestamp; coarser units resolve to
`YYYY-MM-DD`.
Expand DownExpand Up@@ -2053,11 +2051,11 @@ the selected row in `list_item` contexts.
"My Records" filter using `$currentUser`.

5. **Putting widget grid placement in `position`.**
The grid-placement field is `layout: { x, y, w, h }` — there is no
`position` key on a widget, so a `position` object is silently dropped.
`layout` is optional: omit it and the widget auto-flows (the Studio
designer relies on this); set it only when you want an explicit grid
position.
The grid-placement field is `layout: { x, y, w, h }` — `position` is not a
widget key and the closed schema REJECTS it by name (it was silently
dropped before protocol 17). `layout` is optional: omit it and the widget
auto-flows (the Studio designer relies on this); set it only when you want
an explicit grid position.

---

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 22 additions & 24 deletions skills/objectstack-ui/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,7 @@ App navigation, Dashboards, Reports, and Actions.
| `map` | Geospatial records with `location` fields |
| `chart` | Aggregate visualisation over the object (mini chart view) |
| `tree` | Self-referencing hierarchy (tree-grid) |
| `page` | Mounts a published Page (`pageName`); no rows of its own |

### Form Views

Expand DownExpand Up@@ -322,11 +323,11 @@ Rules:
Want both demos? Put them on different views.
- **On an object list view (`*.view.ts` `list` / `listViews`), only
`element: 'dropdown'` (value chips) is allowed — `tabs` is page-only**
(ADR-0047 amendment). An object view's
saved-view `ViewTabBar` already owns the tab-bar role, so a `tabs`
user-filter would render a second, colliding tab bar. The spec narrows it
(`ObjectUserFiltersSchema` — a `tabs` element is untypable at author time
and dropped at parse) and the `validate` list-view-mode lint reports it.
(ADR-0047 amendment). An object view's saved-view `ViewTabBar` already owns
the tab-bar role, so a `tabs` user-filter would render a second, colliding
tab bar. The spec narrows it (`ObjectUserFiltersSchema` — a `tabs` element is
untypable at author time and **rejected** at parse, not dropped) and the
`validate` list-view-mode lint reports it.
Need named presets on an object? Add a `listViews` entry instead. The full
`dropdown | tabs | toggle` range applies only to **page lists** /
`interfaceConfig.userFilters` (the block above).
Expand DownExpand Up@@ -558,7 +559,7 @@ Field shapes:
| `bands[].label` | yes | Header text for the band (白班 / 夜班). |
| `bands[].start` / `bands[].end` | yes | `'HH:mm'`. When `end <= start` the band crosses midnight. Bands must tile the 24h day from `dayStart`. |
| `bands[].color` | no | Any CSS color. Tints that band's column; **omit for no tint**. |
| `showMidnight` | no | Draw the dashed calendar-midnight cue inside cross-midnight bands. Default `true`; set `false` to hide it. |
| `showMidnight` | no | Draw the dashed local-0:00 cue inside cross-midnight bands (the 排班日 cell itself stays unbroken). Default `true`; set `false` to hide it. |

Behavior:

Expand All@@ -570,11 +571,7 @@ Behavior:
in, so a 夜班 spanning 20:00→次日08:00 stays in a single column.
- **Drag-snaps to band boundaries** (the band duration, e.g. 12h) instead of
whole days.
- **Calendar-midnight cue.** A subtle dashed vertical line marks local 0:00
*inside* a cross-midnight band — the 排班日 cell itself stays unbroken. Set
`showMidnight: false` to turn it off.
- **Default off = zero regression.** Omit `timeSegments` and the gantt behaves
exactly as before. Tints render only for bands that declare `color`.
- **Default off.** Omit `timeSegments` and the gantt renders unsegmented.

---

Expand DownExpand Up@@ -669,6 +666,7 @@ A widget's `type` is its **chart type** (`ChartTypeSchema`; defaults to
| Single value | `metric`, `kpi`, `gauge`, `solid-gauge`, `bullet` (all render the number today; gauge variants gain a dial when a gauge renderer lands) |
| Comparison | `bar`, `horizontal-bar`, `column` |
| Trend | `line`, `area` |
| Mixed | `combo` (bar/line/area on shared dual axes) |
| Distribution | `pie`, `donut`, `funnel` |
| Relationship | `scatter` |
| Composition | `treemap`, `sankey` |
Expand All@@ -688,9 +686,9 @@ dashboard widget, a report, and a list `type:'chart'` view all **bind a `dataset
and select named `dimensions` + `values`**; the dataset owns the base object,
allowed joins, intrinsic filter, dimensions, and certified measures. The legacy
per-widget inline query (`object` + `categoryField` + `valueField` + `aggregate`)
**was removed** — a widget now requires `dataset` + `values`; the inline fields are
dropped and a widget lacking `dataset` fails `os validate`. Reports bind the same
way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
**was removed** — a widget now requires `dataset` + `values`; the closed schema
REJECTS the inline keys by name, and one lacking `dataset` fails `os validate`.
Reports bind the same way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
`DatasetSchema` — see `node_modules/@objectstack/spec/src/ui/dataset.zod.ts`.

A widget's presentation-scope `filter` flows into the query as the runtime
Expand DownExpand Up@@ -1523,9 +1521,9 @@ compareTo: { kind: 'previousYear', dimension: 'close_date' } // several —
and overwrites it.
* **Cartesian charts** (`line` / `area` / `bar` / `horizontal-bar` /
`scatter`) — the comparison series is appended after the primary series
with `variant: 'comparison'` and styled as a muted overlay (`opacity: 0.5`
+ `strokeDasharray: '4 4'` for line/area/scatter; `opacity: 0.4` for
bars). Override per-series with `series.dashArray` / `series.opacity`.
with `variant: 'comparison'`, muted per family (dashed `'4 4'` on
line/area only; reduced opacity on all). Override per-series with
`series.dashArray` / `series.opacity`.
* **Pie / donut / funnel** — `compareTo` is silently ignored; there is no
meaningful "two-period" composition for part-of-whole charts.
* **Requirements** — a comparison needs a **dated window** to shift. When the
Expand DownExpand Up@@ -1663,12 +1661,12 @@ Both `{token}` and `${token}` forms are accepted.
| Instants | `today`, `yesterday`, `tomorrow`, `now` |
| Current period | `current_week_start` / `_end`, `current_month_start` / `_end`, `current_quarter_start` / `_end`, `current_year_start` / `_end` |
| Last period | `last_week_start` / `_end`, `last_month_start` / `_end`, `last_quarter_start` / `_end`, `last_year_start` / `_end` |
| Next period | `next_week_start`, `next_month_start`, `next_quarter_start`, `next_year_start` |
| Next period | `next_week_start` / `_end`, `next_month_start` / `_end`, `next_quarter_start` / `_end`, `next_year_start` / `_end` |
| Bare aliases | `week_start`, `week_end`, `month_start`, `month_end`, `quarter_start`, `quarter_end`, `year_start`, `year_end` (same as `current_*`) |

### Parameterised tokens — `{N_<unit>_(ago|from_now)}`

`N` is any positive integer; `<unit>` is one of
`N` is any non-negative integer; `<unit>` is one of
`minute(s) | hour(s) | day(s) | week(s) | month(s) | year(s)`.
`minute`/`hour` resolve to a full ISO timestamp; coarser units resolve to
`YYYY-MM-DD`.
Expand DownExpand Up@@ -2053,11 +2051,11 @@ the selected row in `list_item` contexts.
"My Records" filter using `$currentUser`.

5. **Putting widget grid placement in `position`.**
The grid-placement field is `layout: { x, y, w, h }` — there is no
`position` key on a widget, so a `position` object is silently dropped.
`layout` is optional: omit it and the widget auto-flows (the Studio
designer relies on this); set it only when you want an explicit grid
position.
The grid-placement field is `layout: { x, y, w, h }` — `position` is not a
widget key and the closed schema REJECTS it by name (it was silently
dropped before protocol 17). `layout` is optional: omit it and the widget
auto-flows (the Studio designer relies on this); set it only when you want
an explicit grid position.

---

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 22 additions & 24 deletions skills/objectstack-ui/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,7 @@ App navigation, Dashboards, Reports, and Actions.
| `map` | Geospatial records with `location` fields |
| `chart` | Aggregate visualisation over the object (mini chart view) |
| `tree` | Self-referencing hierarchy (tree-grid) |
| `page` | Mounts a published Page (`pageName`); no rows of its own |

### Form Views

Expand DownExpand Up@@ -322,11 +323,11 @@ Rules:
Want both demos? Put them on different views.
- **On an object list view (`*.view.ts` `list` / `listViews`), only
`element: 'dropdown'` (value chips) is allowed — `tabs` is page-only**
(ADR-0047 amendment). An object view's
saved-view `ViewTabBar` already owns the tab-bar role, so a `tabs`
user-filter would render a second, colliding tab bar. The spec narrows it
(`ObjectUserFiltersSchema` — a `tabs` element is untypable at author time
and dropped at parse) and the `validate` list-view-mode lint reports it.
(ADR-0047 amendment). An object view's saved-view `ViewTabBar` already owns
the tab-bar role, so a `tabs` user-filter would render a second, colliding
tab bar. The spec narrows it (`ObjectUserFiltersSchema` — a `tabs` element is
untypable at author time and **rejected** at parse, not dropped) and the
`validate` list-view-mode lint reports it.
Need named presets on an object? Add a `listViews` entry instead. The full
`dropdown | tabs | toggle` range applies only to **page lists** /
`interfaceConfig.userFilters` (the block above).
Expand DownExpand Up@@ -558,7 +559,7 @@ Field shapes:
| `bands[].label` | yes | Header text for the band (白班 / 夜班). |
| `bands[].start` / `bands[].end` | yes | `'HH:mm'`. When `end <= start` the band crosses midnight. Bands must tile the 24h day from `dayStart`. |
| `bands[].color` | no | Any CSS color. Tints that band's column; **omit for no tint**. |
| `showMidnight` | no | Draw the dashed calendar-midnight cue inside cross-midnight bands. Default `true`; set `false` to hide it. |
| `showMidnight` | no | Draw the dashed local-0:00 cue inside cross-midnight bands (the 排班日 cell itself stays unbroken). Default `true`; set `false` to hide it. |

Behavior:

Expand All@@ -570,11 +571,7 @@ Behavior:
in, so a 夜班 spanning 20:00→次日08:00 stays in a single column.
- **Drag-snaps to band boundaries** (the band duration, e.g. 12h) instead of
whole days.
- **Calendar-midnight cue.** A subtle dashed vertical line marks local 0:00
*inside* a cross-midnight band — the 排班日 cell itself stays unbroken. Set
`showMidnight: false` to turn it off.
- **Default off = zero regression.** Omit `timeSegments` and the gantt behaves
exactly as before. Tints render only for bands that declare `color`.
- **Default off.** Omit `timeSegments` and the gantt renders unsegmented.

---

Expand DownExpand Up@@ -669,6 +666,7 @@ A widget's `type` is its **chart type** (`ChartTypeSchema`; defaults to
| Single value | `metric`, `kpi`, `gauge`, `solid-gauge`, `bullet` (all render the number today; gauge variants gain a dial when a gauge renderer lands) |
| Comparison | `bar`, `horizontal-bar`, `column` |
| Trend | `line`, `area` |
| Mixed | `combo` (bar/line/area on shared dual axes) |
| Distribution | `pie`, `donut`, `funnel` |
| Relationship | `scatter` |
| Composition | `treemap`, `sankey` |
Expand All@@ -688,9 +686,9 @@ dashboard widget, a report, and a list `type:'chart'` view all **bind a `dataset
and select named `dimensions` + `values`**; the dataset owns the base object,
allowed joins, intrinsic filter, dimensions, and certified measures. The legacy
per-widget inline query (`object` + `categoryField` + `valueField` + `aggregate`)
**was removed** — a widget now requires `dataset` + `values`; the inline fields are
dropped and a widget lacking `dataset` fails `os validate`. Reports bind the same
way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
**was removed** — a widget now requires `dataset` + `values`; the closed schema
REJECTS the inline keys by name, and one lacking `dataset` fails `os validate`.
Reports bind the same way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
`DatasetSchema` — see `node_modules/@objectstack/spec/src/ui/dataset.zod.ts`.

A widget's presentation-scope `filter` flows into the query as the runtime
Expand DownExpand Up@@ -1523,9 +1521,9 @@ compareTo: { kind: 'previousYear', dimension: 'close_date' } // several —
and overwrites it.
* **Cartesian charts** (`line` / `area` / `bar` / `horizontal-bar` /
`scatter`) — the comparison series is appended after the primary series
with `variant: 'comparison'` and styled as a muted overlay (`opacity: 0.5`
+ `strokeDasharray: '4 4'` for line/area/scatter; `opacity: 0.4` for
bars). Override per-series with `series.dashArray` / `series.opacity`.
with `variant: 'comparison'`, muted per family (dashed `'4 4'` on
line/area only; reduced opacity on all). Override per-series with
`series.dashArray` / `series.opacity`.
* **Pie / donut / funnel** — `compareTo` is silently ignored; there is no
meaningful "two-period" composition for part-of-whole charts.
* **Requirements** — a comparison needs a **dated window** to shift. When the
Expand DownExpand Up@@ -1663,12 +1661,12 @@ Both `{token}` and `${token}` forms are accepted.
| Instants | `today`, `yesterday`, `tomorrow`, `now` |
| Current period | `current_week_start` / `_end`, `current_month_start` / `_end`, `current_quarter_start` / `_end`, `current_year_start` / `_end` |
| Last period | `last_week_start` / `_end`, `last_month_start` / `_end`, `last_quarter_start` / `_end`, `last_year_start` / `_end` |
| Next period | `next_week_start`, `next_month_start`, `next_quarter_start`, `next_year_start` |
| Next period | `next_week_start` / `_end`, `next_month_start` / `_end`, `next_quarter_start` / `_end`, `next_year_start` / `_end` |
| Bare aliases | `week_start`, `week_end`, `month_start`, `month_end`, `quarter_start`, `quarter_end`, `year_start`, `year_end` (same as `current_*`) |

### Parameterised tokens — `{N_<unit>_(ago|from_now)}`

`N` is any positive integer; `<unit>` is one of
`N` is any non-negative integer; `<unit>` is one of
`minute(s) | hour(s) | day(s) | week(s) | month(s) | year(s)`.
`minute`/`hour` resolve to a full ISO timestamp; coarser units resolve to
`YYYY-MM-DD`.
Expand DownExpand Up@@ -2053,11 +2051,11 @@ the selected row in `list_item` contexts.
"My Records" filter using `$currentUser`.

5. **Putting widget grid placement in `position`.**
The grid-placement field is `layout: { x, y, w, h }` — there is no
`position` key on a widget, so a `position` object is silently dropped.
`layout` is optional: omit it and the widget auto-flows (the Studio
designer relies on this); set it only when you want an explicit grid
position.
The grid-placement field is `layout: { x, y, w, h }` — `position` is not a
widget key and the closed schema REJECTS it by name (it was silently
dropped before protocol 17). `layout` is optional: omit it and the widget
auto-flows (the Studio designer relies on this); set it only when you want
an explicit grid position.

---

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 22 additions & 24 deletions skills/objectstack-ui/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,7 @@ App navigation, Dashboards, Reports, and Actions.
| `map` | Geospatial records with `location` fields |
| `chart` | Aggregate visualisation over the object (mini chart view) |
| `tree` | Self-referencing hierarchy (tree-grid) |
| `page` | Mounts a published Page (`pageName`); no rows of its own |

### Form Views

Expand DownExpand Up@@ -322,11 +323,11 @@ Rules:
Want both demos? Put them on different views.
- **On an object list view (`*.view.ts` `list` / `listViews`), only
`element: 'dropdown'` (value chips) is allowed — `tabs` is page-only**
(ADR-0047 amendment). An object view's
saved-view `ViewTabBar` already owns the tab-bar role, so a `tabs`
user-filter would render a second, colliding tab bar. The spec narrows it
(`ObjectUserFiltersSchema` — a `tabs` element is untypable at author time
and dropped at parse) and the `validate` list-view-mode lint reports it.
(ADR-0047 amendment). An object view's saved-view `ViewTabBar` already owns
the tab-bar role, so a `tabs` user-filter would render a second, colliding
tab bar. The spec narrows it (`ObjectUserFiltersSchema` — a `tabs` element is
untypable at author time and **rejected** at parse, not dropped) and the
`validate` list-view-mode lint reports it.
Need named presets on an object? Add a `listViews` entry instead. The full
`dropdown | tabs | toggle` range applies only to **page lists** /
`interfaceConfig.userFilters` (the block above).
Expand DownExpand Up@@ -558,7 +559,7 @@ Field shapes:
| `bands[].label` | yes | Header text for the band (白班 / 夜班). |
| `bands[].start` / `bands[].end` | yes | `'HH:mm'`. When `end <= start` the band crosses midnight. Bands must tile the 24h day from `dayStart`. |
| `bands[].color` | no | Any CSS color. Tints that band's column; **omit for no tint**. |
| `showMidnight` | no | Draw the dashed calendar-midnight cue inside cross-midnight bands. Default `true`; set `false` to hide it. |
| `showMidnight` | no | Draw the dashed local-0:00 cue inside cross-midnight bands (the 排班日 cell itself stays unbroken). Default `true`; set `false` to hide it. |

Behavior:

Expand All@@ -570,11 +571,7 @@ Behavior:
in, so a 夜班 spanning 20:00→次日08:00 stays in a single column.
- **Drag-snaps to band boundaries** (the band duration, e.g. 12h) instead of
whole days.
- **Calendar-midnight cue.** A subtle dashed vertical line marks local 0:00
*inside* a cross-midnight band — the 排班日 cell itself stays unbroken. Set
`showMidnight: false` to turn it off.
- **Default off = zero regression.** Omit `timeSegments` and the gantt behaves
exactly as before. Tints render only for bands that declare `color`.
- **Default off.** Omit `timeSegments` and the gantt renders unsegmented.

---

Expand DownExpand Up@@ -669,6 +666,7 @@ A widget's `type` is its **chart type** (`ChartTypeSchema`; defaults to
| Single value | `metric`, `kpi`, `gauge`, `solid-gauge`, `bullet` (all render the number today; gauge variants gain a dial when a gauge renderer lands) |
| Comparison | `bar`, `horizontal-bar`, `column` |
| Trend | `line`, `area` |
| Mixed | `combo` (bar/line/area on shared dual axes) |
| Distribution | `pie`, `donut`, `funnel` |
| Relationship | `scatter` |
| Composition | `treemap`, `sankey` |
Expand All@@ -688,9 +686,9 @@ dashboard widget, a report, and a list `type:'chart'` view all **bind a `dataset
and select named `dimensions` + `values`**; the dataset owns the base object,
allowed joins, intrinsic filter, dimensions, and certified measures. The legacy
per-widget inline query (`object` + `categoryField` + `valueField` + `aggregate`)
**was removed** — a widget now requires `dataset` + `values`; the inline fields are
dropped and a widget lacking `dataset` fails `os validate`. Reports bind the same
way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
**was removed** — a widget now requires `dataset` + `values`; the closed schema
REJECTS the inline keys by name, and one lacking `dataset` fails `os validate`.
Reports bind the same way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
`DatasetSchema` — see `node_modules/@objectstack/spec/src/ui/dataset.zod.ts`.

A widget's presentation-scope `filter` flows into the query as the runtime
Expand DownExpand Up@@ -1523,9 +1521,9 @@ compareTo: { kind: 'previousYear', dimension: 'close_date' } // several —
and overwrites it.
* **Cartesian charts** (`line` / `area` / `bar` / `horizontal-bar` /
`scatter`) — the comparison series is appended after the primary series
with `variant: 'comparison'` and styled as a muted overlay (`opacity: 0.5`
+ `strokeDasharray: '4 4'` for line/area/scatter; `opacity: 0.4` for
bars). Override per-series with `series.dashArray` / `series.opacity`.
with `variant: 'comparison'`, muted per family (dashed `'4 4'` on
line/area only; reduced opacity on all). Override per-series with
`series.dashArray` / `series.opacity`.
* **Pie / donut / funnel** — `compareTo` is silently ignored; there is no
meaningful "two-period" composition for part-of-whole charts.
* **Requirements** — a comparison needs a **dated window** to shift. When the
Expand DownExpand Up@@ -1663,12 +1661,12 @@ Both `{token}` and `${token}` forms are accepted.
| Instants | `today`, `yesterday`, `tomorrow`, `now` |
| Current period | `current_week_start` / `_end`, `current_month_start` / `_end`, `current_quarter_start` / `_end`, `current_year_start` / `_end` |
| Last period | `last_week_start` / `_end`, `last_month_start` / `_end`, `last_quarter_start` / `_end`, `last_year_start` / `_end` |
| Next period | `next_week_start`, `next_month_start`, `next_quarter_start`, `next_year_start` |
| Next period | `next_week_start` / `_end`, `next_month_start` / `_end`, `next_quarter_start` / `_end`, `next_year_start` / `_end` |
| Bare aliases | `week_start`, `week_end`, `month_start`, `month_end`, `quarter_start`, `quarter_end`, `year_start`, `year_end` (same as `current_*`) |

### Parameterised tokens — `{N_<unit>_(ago|from_now)}`

`N` is any positive integer; `<unit>` is one of
`N` is any non-negative integer; `<unit>` is one of
`minute(s) | hour(s) | day(s) | week(s) | month(s) | year(s)`.
`minute`/`hour` resolve to a full ISO timestamp; coarser units resolve to
`YYYY-MM-DD`.
Expand DownExpand Up@@ -2053,11 +2051,11 @@ the selected row in `list_item` contexts.
"My Records" filter using `$currentUser`.

5. **Putting widget grid placement in `position`.**
The grid-placement field is `layout: { x, y, w, h }` — there is no
`position` key on a widget, so a `position` object is silently dropped.
`layout` is optional: omit it and the widget auto-flows (the Studio
designer relies on this); set it only when you want an explicit grid
position.
The grid-placement field is `layout: { x, y, w, h }` — `position` is not a
widget key and the closed schema REJECTS it by name (it was silently
dropped before protocol 17). `layout` is optional: omit it and the widget
auto-flows (the Studio designer relies on this); set it only when you want
an explicit grid position.

---

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 22 additions & 24 deletions skills/objectstack-ui/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,7 @@ App navigation, Dashboards, Reports, and Actions.
| `map` | Geospatial records with `location` fields |
| `chart` | Aggregate visualisation over the object (mini chart view) |
| `tree` | Self-referencing hierarchy (tree-grid) |
| `page` | Mounts a published Page (`pageName`); no rows of its own |

### Form Views

Expand DownExpand Up@@ -322,11 +323,11 @@ Rules:
Want both demos? Put them on different views.
- **On an object list view (`*.view.ts` `list` / `listViews`), only
`element: 'dropdown'` (value chips) is allowed — `tabs` is page-only**
(ADR-0047 amendment). An object view's
saved-view `ViewTabBar` already owns the tab-bar role, so a `tabs`
user-filter would render a second, colliding tab bar. The spec narrows it
(`ObjectUserFiltersSchema` — a `tabs` element is untypable at author time
and dropped at parse) and the `validate` list-view-mode lint reports it.
(ADR-0047 amendment). An object view's saved-view `ViewTabBar` already owns
the tab-bar role, so a `tabs` user-filter would render a second, colliding
tab bar. The spec narrows it (`ObjectUserFiltersSchema` — a `tabs` element is
untypable at author time and **rejected** at parse, not dropped) and the
`validate` list-view-mode lint reports it.
Need named presets on an object? Add a `listViews` entry instead. The full
`dropdown | tabs | toggle` range applies only to **page lists** /
`interfaceConfig.userFilters` (the block above).
Expand DownExpand Up@@ -558,7 +559,7 @@ Field shapes:
| `bands[].label` | yes | Header text for the band (白班 / 夜班). |
| `bands[].start` / `bands[].end` | yes | `'HH:mm'`. When `end <= start` the band crosses midnight. Bands must tile the 24h day from `dayStart`. |
| `bands[].color` | no | Any CSS color. Tints that band's column; **omit for no tint**. |
| `showMidnight` | no | Draw the dashed calendar-midnight cue inside cross-midnight bands. Default `true`; set `false` to hide it. |
| `showMidnight` | no | Draw the dashed local-0:00 cue inside cross-midnight bands (the 排班日 cell itself stays unbroken). Default `true`; set `false` to hide it. |

Behavior:

Expand All@@ -570,11 +571,7 @@ Behavior:
in, so a 夜班 spanning 20:00→次日08:00 stays in a single column.
- **Drag-snaps to band boundaries** (the band duration, e.g. 12h) instead of
whole days.
- **Calendar-midnight cue.** A subtle dashed vertical line marks local 0:00
*inside* a cross-midnight band — the 排班日 cell itself stays unbroken. Set
`showMidnight: false` to turn it off.
- **Default off = zero regression.** Omit `timeSegments` and the gantt behaves
exactly as before. Tints render only for bands that declare `color`.
- **Default off.** Omit `timeSegments` and the gantt renders unsegmented.

---

Expand DownExpand Up@@ -669,6 +666,7 @@ A widget's `type` is its **chart type** (`ChartTypeSchema`; defaults to
| Single value | `metric`, `kpi`, `gauge`, `solid-gauge`, `bullet` (all render the number today; gauge variants gain a dial when a gauge renderer lands) |
| Comparison | `bar`, `horizontal-bar`, `column` |
| Trend | `line`, `area` |
| Mixed | `combo` (bar/line/area on shared dual axes) |
| Distribution | `pie`, `donut`, `funnel` |
| Relationship | `scatter` |
| Composition | `treemap`, `sankey` |
Expand All@@ -688,9 +686,9 @@ dashboard widget, a report, and a list `type:'chart'` view all **bind a `dataset
and select named `dimensions` + `values`**; the dataset owns the base object,
allowed joins, intrinsic filter, dimensions, and certified measures. The legacy
per-widget inline query (`object` + `categoryField` + `valueField` + `aggregate`)
**was removed** — a widget now requires `dataset` + `values`; the inline fields are
dropped and a widget lacking `dataset` fails `os validate`. Reports bind the same
way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
**was removed** — a widget now requires `dataset` + `values`; the closed schema
REJECTS the inline keys by name, and one lacking `dataset` fails `os validate`.
Reports bind the same way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
`DatasetSchema` — see `node_modules/@objectstack/spec/src/ui/dataset.zod.ts`.

A widget's presentation-scope `filter` flows into the query as the runtime
Expand DownExpand Up@@ -1523,9 +1521,9 @@ compareTo: { kind: 'previousYear', dimension: 'close_date' } // several —
and overwrites it.
* **Cartesian charts** (`line` / `area` / `bar` / `horizontal-bar` /
`scatter`) — the comparison series is appended after the primary series
with `variant: 'comparison'` and styled as a muted overlay (`opacity: 0.5`
+ `strokeDasharray: '4 4'` for line/area/scatter; `opacity: 0.4` for
bars). Override per-series with `series.dashArray` / `series.opacity`.
with `variant: 'comparison'`, muted per family (dashed `'4 4'` on
line/area only; reduced opacity on all). Override per-series with
`series.dashArray` / `series.opacity`.
* **Pie / donut / funnel** — `compareTo` is silently ignored; there is no
meaningful "two-period" composition for part-of-whole charts.
* **Requirements** — a comparison needs a **dated window** to shift. When the
Expand DownExpand Up@@ -1663,12 +1661,12 @@ Both `{token}` and `${token}` forms are accepted.
| Instants | `today`, `yesterday`, `tomorrow`, `now` |
| Current period | `current_week_start` / `_end`, `current_month_start` / `_end`, `current_quarter_start` / `_end`, `current_year_start` / `_end` |
| Last period | `last_week_start` / `_end`, `last_month_start` / `_end`, `last_quarter_start` / `_end`, `last_year_start` / `_end` |
| Next period | `next_week_start`, `next_month_start`, `next_quarter_start`, `next_year_start` |
| Next period | `next_week_start` / `_end`, `next_month_start` / `_end`, `next_quarter_start` / `_end`, `next_year_start` / `_end` |
| Bare aliases | `week_start`, `week_end`, `month_start`, `month_end`, `quarter_start`, `quarter_end`, `year_start`, `year_end` (same as `current_*`) |

### Parameterised tokens — `{N_<unit>_(ago|from_now)}`

`N` is any positive integer; `<unit>` is one of
`N` is any non-negative integer; `<unit>` is one of
`minute(s) | hour(s) | day(s) | week(s) | month(s) | year(s)`.
`minute`/`hour` resolve to a full ISO timestamp; coarser units resolve to
`YYYY-MM-DD`.
Expand DownExpand Up@@ -2053,11 +2051,11 @@ the selected row in `list_item` contexts.
"My Records" filter using `$currentUser`.

5. **Putting widget grid placement in `position`.**
The grid-placement field is `layout: { x, y, w, h }` — there is no
`position` key on a widget, so a `position` object is silently dropped.
`layout` is optional: omit it and the widget auto-flows (the Studio
designer relies on this); set it only when you want an explicit grid
position.
The grid-placement field is `layout: { x, y, w, h }` — `position` is not a
widget key and the closed schema REJECTS it by name (it was silently
dropped before protocol 17). `layout` is optional: omit it and the widget
auto-flows (the Studio designer relies on this); set it only when you want
an explicit grid position.

---

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 22 additions & 24 deletions skills/objectstack-ui/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,7 @@ App navigation, Dashboards, Reports, and Actions.
| `map` | Geospatial records with `location` fields |
| `chart` | Aggregate visualisation over the object (mini chart view) |
| `tree` | Self-referencing hierarchy (tree-grid) |
| `page` | Mounts a published Page (`pageName`); no rows of its own |

### Form Views

Expand DownExpand Up@@ -322,11 +323,11 @@ Rules:
Want both demos? Put them on different views.
- **On an object list view (`*.view.ts` `list` / `listViews`), only
`element: 'dropdown'` (value chips) is allowed — `tabs` is page-only**
(ADR-0047 amendment). An object view's
saved-view `ViewTabBar` already owns the tab-bar role, so a `tabs`
user-filter would render a second, colliding tab bar. The spec narrows it
(`ObjectUserFiltersSchema` — a `tabs` element is untypable at author time
and dropped at parse) and the `validate` list-view-mode lint reports it.
(ADR-0047 amendment). An object view's saved-view `ViewTabBar` already owns
the tab-bar role, so a `tabs` user-filter would render a second, colliding
tab bar. The spec narrows it (`ObjectUserFiltersSchema` — a `tabs` element is
untypable at author time and **rejected** at parse, not dropped) and the
`validate` list-view-mode lint reports it.
Need named presets on an object? Add a `listViews` entry instead. The full
`dropdown | tabs | toggle` range applies only to **page lists** /
`interfaceConfig.userFilters` (the block above).
Expand DownExpand Up@@ -558,7 +559,7 @@ Field shapes:
| `bands[].label` | yes | Header text for the band (白班 / 夜班). |
| `bands[].start` / `bands[].end` | yes | `'HH:mm'`. When `end <= start` the band crosses midnight. Bands must tile the 24h day from `dayStart`. |
| `bands[].color` | no | Any CSS color. Tints that band's column; **omit for no tint**. |
| `showMidnight` | no | Draw the dashed calendar-midnight cue inside cross-midnight bands. Default `true`; set `false` to hide it. |
| `showMidnight` | no | Draw the dashed local-0:00 cue inside cross-midnight bands (the 排班日 cell itself stays unbroken). Default `true`; set `false` to hide it. |

Behavior:

Expand All@@ -570,11 +571,7 @@ Behavior:
in, so a 夜班 spanning 20:00→次日08:00 stays in a single column.
- **Drag-snaps to band boundaries** (the band duration, e.g. 12h) instead of
whole days.
- **Calendar-midnight cue.** A subtle dashed vertical line marks local 0:00
*inside* a cross-midnight band — the 排班日 cell itself stays unbroken. Set
`showMidnight: false` to turn it off.
- **Default off = zero regression.** Omit `timeSegments` and the gantt behaves
exactly as before. Tints render only for bands that declare `color`.
- **Default off.** Omit `timeSegments` and the gantt renders unsegmented.

---

Expand DownExpand Up@@ -669,6 +666,7 @@ A widget's `type` is its **chart type** (`ChartTypeSchema`; defaults to
| Single value | `metric`, `kpi`, `gauge`, `solid-gauge`, `bullet` (all render the number today; gauge variants gain a dial when a gauge renderer lands) |
| Comparison | `bar`, `horizontal-bar`, `column` |
| Trend | `line`, `area` |
| Mixed | `combo` (bar/line/area on shared dual axes) |
| Distribution | `pie`, `donut`, `funnel` |
| Relationship | `scatter` |
| Composition | `treemap`, `sankey` |
Expand All@@ -688,9 +686,9 @@ dashboard widget, a report, and a list `type:'chart'` view all **bind a `dataset
and select named `dimensions` + `values`**; the dataset owns the base object,
allowed joins, intrinsic filter, dimensions, and certified measures. The legacy
per-widget inline query (`object` + `categoryField` + `valueField` + `aggregate`)
**was removed** — a widget now requires `dataset` + `values`; the inline fields are
dropped and a widget lacking `dataset` fails `os validate`. Reports bind the same
way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
**was removed** — a widget now requires `dataset` + `values`; the closed schema
REJECTS the inline keys by name, and one lacking `dataset` fails `os validate`.
Reports bind the same way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
`DatasetSchema` — see `node_modules/@objectstack/spec/src/ui/dataset.zod.ts`.

A widget's presentation-scope `filter` flows into the query as the runtime
Expand DownExpand Up@@ -1523,9 +1521,9 @@ compareTo: { kind: 'previousYear', dimension: 'close_date' } // several —
and overwrites it.
* **Cartesian charts** (`line` / `area` / `bar` / `horizontal-bar` /
`scatter`) — the comparison series is appended after the primary series
with `variant: 'comparison'` and styled as a muted overlay (`opacity: 0.5`
+ `strokeDasharray: '4 4'` for line/area/scatter; `opacity: 0.4` for
bars). Override per-series with `series.dashArray` / `series.opacity`.
with `variant: 'comparison'`, muted per family (dashed `'4 4'` on
line/area only; reduced opacity on all). Override per-series with
`series.dashArray` / `series.opacity`.
* **Pie / donut / funnel** — `compareTo` is silently ignored; there is no
meaningful "two-period" composition for part-of-whole charts.
* **Requirements** — a comparison needs a **dated window** to shift. When the
Expand DownExpand Up@@ -1663,12 +1661,12 @@ Both `{token}` and `${token}` forms are accepted.
| Instants | `today`, `yesterday`, `tomorrow`, `now` |
| Current period | `current_week_start` / `_end`, `current_month_start` / `_end`, `current_quarter_start` / `_end`, `current_year_start` / `_end` |
| Last period | `last_week_start` / `_end`, `last_month_start` / `_end`, `last_quarter_start` / `_end`, `last_year_start` / `_end` |
| Next period | `next_week_start`, `next_month_start`, `next_quarter_start`, `next_year_start` |
| Next period | `next_week_start` / `_end`, `next_month_start` / `_end`, `next_quarter_start` / `_end`, `next_year_start` / `_end` |
| Bare aliases | `week_start`, `week_end`, `month_start`, `month_end`, `quarter_start`, `quarter_end`, `year_start`, `year_end` (same as `current_*`) |

### Parameterised tokens — `{N_<unit>_(ago|from_now)}`

`N` is any positive integer; `<unit>` is one of
`N` is any non-negative integer; `<unit>` is one of
`minute(s) | hour(s) | day(s) | week(s) | month(s) | year(s)`.
`minute`/`hour` resolve to a full ISO timestamp; coarser units resolve to
`YYYY-MM-DD`.
Expand DownExpand Up@@ -2053,11 +2051,11 @@ the selected row in `list_item` contexts.
"My Records" filter using `$currentUser`.

5. **Putting widget grid placement in `position`.**
The grid-placement field is `layout: { x, y, w, h }` — there is no
`position` key on a widget, so a `position` object is silently dropped.
`layout` is optional: omit it and the widget auto-flows (the Studio
designer relies on this); set it only when you want an explicit grid
position.
The grid-placement field is `layout: { x, y, w, h }` — `position` is not a
widget key and the closed schema REJECTS it by name (it was silently
dropped before protocol 17). `layout` is optional: omit it and the widget
auto-flows (the Studio designer relies on this); set it only when you want
an explicit grid position.

---

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 22 additions & 24 deletions skills/objectstack-ui/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,7 @@ App navigation, Dashboards, Reports, and Actions.
| `map` | Geospatial records with `location` fields |
| `chart` | Aggregate visualisation over the object (mini chart view) |
| `tree` | Self-referencing hierarchy (tree-grid) |
| `page` | Mounts a published Page (`pageName`); no rows of its own |

### Form Views

Expand DownExpand Up@@ -322,11 +323,11 @@ Rules:
Want both demos? Put them on different views.
- **On an object list view (`*.view.ts` `list` / `listViews`), only
`element: 'dropdown'` (value chips) is allowed — `tabs` is page-only**
(ADR-0047 amendment). An object view's
saved-view `ViewTabBar` already owns the tab-bar role, so a `tabs`
user-filter would render a second, colliding tab bar. The spec narrows it
(`ObjectUserFiltersSchema` — a `tabs` element is untypable at author time
and dropped at parse) and the `validate` list-view-mode lint reports it.
(ADR-0047 amendment). An object view's saved-view `ViewTabBar` already owns
the tab-bar role, so a `tabs` user-filter would render a second, colliding
tab bar. The spec narrows it (`ObjectUserFiltersSchema` — a `tabs` element is
untypable at author time and **rejected** at parse, not dropped) and the
`validate` list-view-mode lint reports it.
Need named presets on an object? Add a `listViews` entry instead. The full
`dropdown | tabs | toggle` range applies only to **page lists** /
`interfaceConfig.userFilters` (the block above).
Expand DownExpand Up@@ -558,7 +559,7 @@ Field shapes:
| `bands[].label` | yes | Header text for the band (白班 / 夜班). |
| `bands[].start` / `bands[].end` | yes | `'HH:mm'`. When `end <= start` the band crosses midnight. Bands must tile the 24h day from `dayStart`. |
| `bands[].color` | no | Any CSS color. Tints that band's column; **omit for no tint**. |
| `showMidnight` | no | Draw the dashed calendar-midnight cue inside cross-midnight bands. Default `true`; set `false` to hide it. |
| `showMidnight` | no | Draw the dashed local-0:00 cue inside cross-midnight bands (the 排班日 cell itself stays unbroken). Default `true`; set `false` to hide it. |

Behavior:

Expand All@@ -570,11 +571,7 @@ Behavior:
in, so a 夜班 spanning 20:00→次日08:00 stays in a single column.
- **Drag-snaps to band boundaries** (the band duration, e.g. 12h) instead of
whole days.
- **Calendar-midnight cue.** A subtle dashed vertical line marks local 0:00
*inside* a cross-midnight band — the 排班日 cell itself stays unbroken. Set
`showMidnight: false` to turn it off.
- **Default off = zero regression.** Omit `timeSegments` and the gantt behaves
exactly as before. Tints render only for bands that declare `color`.
- **Default off.** Omit `timeSegments` and the gantt renders unsegmented.

---

Expand DownExpand Up@@ -669,6 +666,7 @@ A widget's `type` is its **chart type** (`ChartTypeSchema`; defaults to
| Single value | `metric`, `kpi`, `gauge`, `solid-gauge`, `bullet` (all render the number today; gauge variants gain a dial when a gauge renderer lands) |
| Comparison | `bar`, `horizontal-bar`, `column` |
| Trend | `line`, `area` |
| Mixed | `combo` (bar/line/area on shared dual axes) |
| Distribution | `pie`, `donut`, `funnel` |
| Relationship | `scatter` |
| Composition | `treemap`, `sankey` |
Expand All@@ -688,9 +686,9 @@ dashboard widget, a report, and a list `type:'chart'` view all **bind a `dataset
and select named `dimensions` + `values`**; the dataset owns the base object,
allowed joins, intrinsic filter, dimensions, and certified measures. The legacy
per-widget inline query (`object` + `categoryField` + `valueField` + `aggregate`)
**was removed** — a widget now requires `dataset` + `values`; the inline fields are
dropped and a widget lacking `dataset` fails `os validate`. Reports bind the same
way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
**was removed** — a widget now requires `dataset` + `values`; the closed schema
REJECTS the inline keys by name, and one lacking `dataset` fails `os validate`.
Reports bind the same way (`dataset` + `rows` + `values` + `runtimeFilter`). The dataset shape is
`DatasetSchema` — see `node_modules/@objectstack/spec/src/ui/dataset.zod.ts`.

A widget's presentation-scope `filter` flows into the query as the runtime
Expand DownExpand Up@@ -1523,9 +1521,9 @@ compareTo: { kind: 'previousYear', dimension: 'close_date' } // several —
and overwrites it.
* **Cartesian charts** (`line` / `area` / `bar` / `horizontal-bar` /
`scatter`) — the comparison series is appended after the primary series
with `variant: 'comparison'` and styled as a muted overlay (`opacity: 0.5`
+ `strokeDasharray: '4 4'` for line/area/scatter; `opacity: 0.4` for
bars). Override per-series with `series.dashArray` / `series.opacity`.
with `variant: 'comparison'`, muted per family (dashed `'4 4'` on
line/area only; reduced opacity on all). Override per-series with
`series.dashArray` / `series.opacity`.
* **Pie / donut / funnel** — `compareTo` is silently ignored; there is no
meaningful "two-period" composition for part-of-whole charts.
* **Requirements** — a comparison needs a **dated window** to shift. When the
Expand DownExpand Up@@ -1663,12 +1661,12 @@ Both `{token}` and `${token}` forms are accepted.
| Instants | `today`, `yesterday`, `tomorrow`, `now` |
| Current period | `current_week_start` / `_end`, `current_month_start` / `_end`, `current_quarter_start` / `_end`, `current_year_start` / `_end` |
| Last period | `last_week_start` / `_end`, `last_month_start` / `_end`, `last_quarter_start` / `_end`, `last_year_start` / `_end` |
| Next period | `next_week_start`, `next_month_start`, `next_quarter_start`, `next_year_start` |
| Next period | `next_week_start` / `_end`, `next_month_start` / `_end`, `next_quarter_start` / `_end`, `next_year_start` / `_end` |
| Bare aliases | `week_start`, `week_end`, `month_start`, `month_end`, `quarter_start`, `quarter_end`, `year_start`, `year_end` (same as `current_*`) |

### Parameterised tokens — `{N_<unit>_(ago|from_now)}`

`N` is any positive integer; `<unit>` is one of
`N` is any non-negative integer; `<unit>` is one of
`minute(s) | hour(s) | day(s) | week(s) | month(s) | year(s)`.
`minute`/`hour` resolve to a full ISO timestamp; coarser units resolve to
`YYYY-MM-DD`.
Expand DownExpand Up@@ -2053,11 +2051,11 @@ the selected row in `list_item` contexts.
"My Records" filter using `$currentUser`.

5. **Putting widget grid placement in `position`.**
The grid-placement field is `layout: { x, y, w, h }` — there is no
`position` key on a widget, so a `position` object is silently dropped.
`layout` is optional: omit it and the widget auto-flows (the Studio
designer relies on this); set it only when you want an explicit grid
position.
The grid-placement field is `layout: { x, y, w, h }` — `position` is not a
widget key and the closed schema REJECTS it by name (it was silently
dropped before protocol 17). `layout` is optional: omit it and the widget
auto-flows (the Studio designer relies on this); set it only when you want
an explicit grid position.

---

Expand Down
Loading