Skip to content

[WIP] Add Kanban board to visualize workflow stages - #198

Closed
hotlong with Copilot wants to merge 1 commit into
mainfrom
copilot/visualize-workflow-stages
Closed

hotlong with Copilot wants to merge 1 commit into
mainfrom
copilot/visualize-workflow-stages

Conversation

Copilot AI commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Thanks for asking me to work on this. I will get started on it and keep this PR's description up to date as I form a plan and make progress.

Original prompt

Kanban Board - Visualize workflow stages
Todo → In Progress → Done
Unknown component type: group
{
"type": "group",
"gap": 2,
"children": [
{
"type": "card",
"className": "p-3 bg-slate-50",
"children": [
{
"type": "stack",
"gap": 1,


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel

vercel Bot commented Jan 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
objectui-docs Error Error Jan 26, 2026 3:24am

@hotlong hotlong closed this Jan 26, 2026
Copilot AI requested a review from hotlong January 26, 2026 03:25
Copilot stopped work on behalf of hotlong due to an error January 26, 2026 03:25
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…nd yields to the view's cap (objectui#10016) (objectstack-ai#10389)

Fixes objectstack-ai#10016
Clause-②: yes

Ruling `5749674763` (batch objectstack-ai#198 item 4, letter **A**) applied. A
`dataSource` binding's own `limit` gets the positivity check
objectui#10014 gave the saved view's operand. A refused binding cap
(`0`, a negative, a fraction, or a non-number) is **not authored**: the
chain falls through to `savedViewLimit(view)`, and when that is absent
too, to the consumer's own default. The rule is objectui#10009's
precedent (a contract-refused value is not authored, so the other source
wins), applied to the two operands of one resolver. The defect carried
on the thread (`5814132982`) is in the same stroke: `ViewDataProvider`
no longer reports a refused view cap when the binding's own usable
`limit` is the cap actually used.

## What changed

- **`composeElementDataSource`**: `config.limit ?? savedViewLimit(view)`
becomes `bindingLimit(config) ?? savedViewLimit(view)`. `bindingLimit`
is module-private and applies the same `isUsableRowLimit` predicate
`savedViewLimit` uses. `null` stays absent, as `??` made it.
- **`elementDataSourceRefusedLimitMessage`** (the channel objectui#10014
built; ⛔ no third channel): it takes two optional trailing parameters,
`binding` and `operand` (`'view'`, the default, or `'binding'`). The
binding operand has its own sentence, built by a module-private sibling,
and that sentence names the binding (`dataSource binding on OBJECT ...
declares limit: VALUE`). So the two refusals are told apart when both
fire. For the view operand, the builder now applies the condition "the
binding supplied no usable cap" itself, so both callers share one copy
of it. **No new export**: `@object-ui/core`'s entry list is
byte-identical, and a three-argument call answers exactly as before.
- **`ViewDataProvider.resolveElementDataSource`** asks both operands and
warns once per refused operand on the existing `console.warn` channel.
- **`ElementDataSourceGate`** (renderer path):
- The view cap that takes a refused binding cap's place is a
view-sourced BASELINE. A usable component cap still wins over it, and
the gate's `fromView` for the row cap is now "the binding has no usable
`limit`".
- The binding refusal is reported once per declaration, from an effect,
for a block that reads a row cap.
  - The view refusal's binding condition moved into the builder.
- The objectui#9928 test's KNOWN GAP header now points at this card's
test file. None of objectui#10014's or objectui#10241's assertions were
rewritten.

## Zone 2 readings (measured on `ea02938cd`, the branch base)

1. `element-data-source.ts` held `const limit = config.limit ??
savedViewLimit(view);` in `composeElementDataSource`, `savedViewLimit`
reading `savedViewRawLimit` through `isUsableRowLimit`, and
`elementDataSourceRefusedLimitMessage(view, viewName, object)`.
**Confirmed.**
2. The callers that reported the view refusal:
- `ViewDataProvider.resolveElementDataSource`, which called
`elementDataSourceRefusedLimitMessage(view, config.view, config.object)`
and warned **unconditionally**;
- the gate's `describeRefusedViewRowLimit`, which returned `null` on
`config.limit != null` or a usable component cap, under `if (limit)`.

**Confirmed.** A third caller of the composer exists:
`useElementDataSource`, whose consumers are the gate and
`element:record_picker`. The record picker reports **neither** refusal
today, so by the dispatch's rule it is out of scope. It is listed under
Acceptance notes.
3. The builder was reused through a parameter, with no new export.
**Confirmed feasible.** The additive parameters sit on an export that
has not been released yet:
`.changeset/9928-saved-view-limit-non-positive.md` is still pending.
4. The truth table follows.

## Truth table, measured before and after on both callers

The instrument was a throwaway probe, not committed. It ran against the
base blobs of the three source files, then against HEAD, and the restore
was proven by blob hash. "none" = no `limit` reached the fetcher, or no
cap was written to the node. Warnings are listed by the operand they
name.

**`ViewDataProvider`** (fetcher `limit`):

| binding | view | before | after |
|---|---|---|---|
| absent | no cap | none, no warning | none, no warning |
| absent | usable 25 | 25 | 25 |
| absent | refused 0 | none, view warning | none, view warning |
| usable 3 | no cap | 3 | 3 |
| usable 3 | usable 25 | 3 | 3 |
| usable 3 | refused 0 | 3, **view warning (false claim)** | 3, **no
warning** |
| refused 0 | no cap | **0** | none, binding warning |
| refused 0 | usable 25 | **0** | **25**, binding warning |
| refused 0 | refused 0 | **0**, view warning | none, binding + view
warnings |

**`ElementDataSourceGate`** (`pagination.pageSize` written; component
cap C):

| C | binding | view | before | after |
|---|---|---|---|---|
| absent | refused 0 | no cap | **0** | none, binding warning |
| absent | refused 0 | usable 7 | **0** | **7**, binding warning |
| absent | refused 0 | refused 0 | **0** | none, view + binding warnings
|
| usable 50 | refused 0 | no cap / usable 7 / refused 0 | **0** (over
the usable 50) | **50**, binding warning |
| refused 0 | refused 0 | no cap | 0, displaced-cap message claiming
"(0) was used" | 0 (the component's own, for the renderer's diagnostic),
binding warning |
| refused 0 | refused 0 | usable 7 | 0, displaced-cap message | **7**,
displaced-cap + binding warnings |
| refused 0 | refused 0 | refused 0 | 0, displaced-cap message | 0, view
+ binding warnings |

The probe covered 27 gate rows (C x binding x view, each three-valued).
The **18 rows without a refused binding are identical before and
after**, in value and in warnings. On the gate, the carried defect's row
(usable 3 x refused 0) was already silent, and it still is. **Only the
rows with a refused binding change resolution. Everywhere else, the one
change is the carried-defect warning on `ViewDataProvider`.** This
matches Zone 2 item 4.

## Tests (HEAD `22feffeb2`)

- New `element-data-source.bindingLimitNotAuthored-10016.test.ts`. It
covers the composer's full table over refused values `0`, `-10`, `25.5`,
`"20"`, and the builder, which names each operand, stays silent under a
usable binding, and gives two distinct messages when both operands are
refused. It also covers every `ViewDataProvider` row, including the
dispatch's five minimum pins.
- A new block in `ElementDataSourceGate.test.tsx` has every
refused-binding row, the component-cap rows, the flat `limit` key, a
re-render guard, the carried-defect row, and CONTROL and SILENCE rows.
- `pnpm exec vitest run packages/core/src/data-scope/
packages/react/src/element-data-source/`: **8 files, 260 tests passed**.
- Union on the final commit, `22feffeb2`: `pnpm exec vitest run` over
the two directories above, plus every other test file that names the
binding, the gate, the hook, the composer or `resolveElementDataSource`
(51 files across plugin-list, grid, kanban, timeline, detail, form,
dashboard, map, calendar, gantt, charts, view, components, app-shell,
console, types and scripts). The union also includes the two tests that
read the touched sources as TEXT:
`injected-component-input-6950.test.ts` and
`registry-assertion-ratchet-8316.test.ts`.
`useResolvedDataSource.schemaRendererContextRead-7209.test.ts` is
already inside the gate directory. Result: **61 files, 1203 tests
passed**.
- `pnpm --filter @object-ui/core --filter @object-ui/react run
type-check` passed. Both echoed `type-check: Done`, after `pnpm --filter
'@object-ui/react^...' run build` rebuilt the closure from HEAD; the
rebuilt core `.d.ts` carries the new `operand` parameter. `tsc -p
tsconfig.test.json --listFiles` counts the new core test file (1) and
the gate test file (1).

### Ablations (each through `ablation-replace.mjs` WRAP mode: the anchor
had to hit x1, the landing was proven by anchor and replacement counts
and a blob change, and the restore was proven by blob == HEAD and an
empty `git diff HEAD`)

`@object-ui/core` is aliased to `packages/core/src` by the root vitest
config, and no `dist/` was on disk during these runs. So each mutation
reached both suites without a build.

- **(a) The binding positivity check was removed** (`bindingLimit`
returns `config.limit`). Blob `cc2aa376` went to `302d2fde` and was
restored to `cc2aa376`. **35 failed / 225 passed**: every
refused-binding row on the composer, `ViewDataProvider` and the gate.
**0 of the failures carry CONTROL or SILENCE.** The two gate rows with a
usable component cap stayed green, because the gate's own `fromView`
condition guards them, hence (c).
- **(b) The unconditional view warning was restored** (the builder's `if
(isUsableRowLimit(binding?.limit)) return null;` was deleted). Blob
`cc2aa376` went to `b7c7c10a` and was restored. **5 failed**: the
`ViewDataProvider` carried-defect row, the builder row, the gate's
carried-defect row, and two objectui#10241 pins whose guard now lives in
the builder. Those two are "the binding's own `limit` wins over the
view" and "a binding `limit` displaced a refused component cap". ⚠️ The
ablation caught a naming error of mine. The gate's carried-defect row
was labelled CONTROL, but under this suite's convention a CONTROL passes
on both sides of the ablated guard, and this row does not. It was
relabelled in a follow-up commit (`22feffeb2`).
- **(c) The gate's `fromView` was reverted to presence**
(`binding.config?.limit === undefined`). Blob `f2082ce4` went to
`c9239886` and was restored. **Exactly 1 failed**: "binding 0 + usable
view cap + usable COMPONENT cap ⇒ the component's cap wins over the view
baseline".

## Gates (each exit code captured before any pipe)

- `pnpm check:control-bytes`: `OK (scanned 8436 tracked text file(s);
skipped 85 binary)`.
- `pnpm check:new-line-citations`: `VERDICT
new-cross-file-line-citations: 0 new citation(s)`.
- `node scripts/check-changeset-presence.mjs`: `6 source file(s) of 2
released package(s) changed, and this change declares 1 changeset(s)`.
- `pnpm check:changeset-claims` passed; it flags nothing. `pnpm
check:pending-changeset-literals` passed. `node
scripts/check-changeset-no-major.mjs` passed.
- `pnpm check:test-path-roots` and `pnpm
check:element-data-source-declaration` (13 gate-consuming files) passed.
- **eslint, narrowed to the diff, as a measurement:**
- ① The population, read from eslint's own config: of the 7 changed
files, eslint lints 6; the changeset `.md` gets "File ignored because no
matching configuration was supplied".
- ② `--format json` returned 6 results with **0 errors and 27
warnings**. The base blobs of the same files give 27 warnings too: 13 +
9 + 5 on the three pre-existing files that carry any, and 0 on the new
file.
- ③ Invariance: type-aware linting is off (`eslint.config.js` has no
`projectService`, no `parserOptions.project` and no TypeChecked preset),
and no rule under `eslint-rules/` reads the filesystem. So this diff
cannot move a verdict on an untouched file.
- **NOT MEASURED**, `check:doc-examples` and `check:readme-exports`.
Reason: `PREREQUISITE NOT MET`, because 32 packages are unbuilt. The
diff touches no `@example` block (0 hits in the diff) and adds no
export. The full farm and repo-wide `pnpm lint` are CI's.

## Population, with a lit control (card item 3)

The instrument was a throwaway walk at `22feffeb2`, over `git ls-files`:
549 strict-JSON files and 257 JSON fences in `.md`/`.mdx` (changesets
and CHANGELOGs excluded), plus a literal scan of `dataSource: { object:
... }` object literals in source.

| population | bindings | with a `limit` (CONTROL: usable) | refused |
|---|--:|--:|--:|
| authored metadata (JSON + fences) | 7 | **5 (5)** | **0** |
| source literals | 124 | **11 (11)** | **0** |

The control is **lit** in both rows, so this zero is a reading, not a
dark instrument. No authored binding in the tree carries a refused
`limit`. This agrees with the ruling's facet ②, which said the change is
not urgent.

**Seat amendment (2026-09-24T21:40Z).** Line 2 now reads `Clause-②:
yes`. The exported `elementDataSourceRefusedLimitMessage` gains two
optional parameters, which changes the `@object-ui/core` exports-map
surface, so a contract review is owed before enqueue (claim
`5822089217`, amended). The `@object-ui/react` changeset level moved
from `patch` to `minor` at `226994a2f`.

## Acceptance notes

- **The write-surface refusal (the ruling's C half):** 承接者:无. It was
measured that the spec contract already refuses the value: on
`@objectstack/spec` 17.4.0, `PageComponentSchema.safeParse` with
`dataSource.limit: 0` gives `too_small` at `dataSource.limit`, and
`ElementDataSourceSchema` refuses `0` / `-10` (`too_small`) and `25.5` /
`"20"` (`invalid_type`). Whether every write surface runs that parse
before persisting was not measured.
- **The third caller of the composer, `element:record_picker`** (through
`useElementDataSource`). It reports neither the view's refusal nor now
the binding's, so by the dispatch's rule it is out of scope. Its
resolution follows the ruling for free (`composed?.limit ?? props.limit
?? 50`). Its registry `description` still states the precedence as
`dataSource.limit ?? limit ?? 50`, which predates both objectui#9928 and
this card. 承接者:无.
- **`content/docs/guide/data-source.md`** says "`sort` and `limit`
override the view's". That is still true for a usable binding `limit`,
and it says nothing about a refused one. It is outside this claim's file
surface. 承接者:无.
- `ReportView` forwards `dataSource.limit` raw as `$top`, but the report
contract declares that key `dataSource?: never` (and the spec's
`ReportSchema` refuses it as an unrecognized key), so it is not this
binding. It was noted and not pursued.
- The objectui#10009 changeset's "an explicit cap on the `dataSource`
binding still wins over both" now holds for a **usable** binding cap.
This PR's changeset states that explicitly.

Changeset: `.changeset/10016-binding-limit-not-authored.md`.
`@object-ui/core` is `minor`, following objectui#10014's level for this
class, and `@object-ui/react` is `minor` since head `226994a2f` (seat
respin; it was `patch`, following objectui#10009 and objectui#10241),
because the gate's precedence changes on the refused-binding rows. The
changeset states the behaviour change plainly.

The implementing session is
`https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC` (`domain:ui`
seat 1 dispatch, `mode:subagent`).

---
_Generated by [Claude
Code](https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC)_

---------

Co-authored-by: Claude <noreply@anthropic.com>

This branch had an error being deployed

1 failed deployment
Preview – objectui-docs — c233fc88 Deployed Jan 26, 2026 by vercel[bot]
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