Skip to content

Commit fa457cd

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-8rp5dl
2 parents c4636b5 + 9ae6384 commit fa457cd

28 files changed

Lines changed: 984 additions & 79 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/skills': patch
3+
'@object-ui/console': patch
4+
---
5+
6+
docs(skills): the objectui data-integration guide stops teaching the MSW plugin removed in 12.0
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
- **Before.** `skills/objectui/guides/data-integration.md` taught a frontend-first setup built on the MSW plugin package, removed in 12.0 (its last npm release is 11.0.0, so the install still succeeds and pulls an 11.x plugin into the current stack), and eval 2 of `evals/data-integration.json` rewarded that answer.
12+
- **Now.** The section "Frontend-first development" names the two supported paths: a static scope with no backend, or the real server run locally with `os dev` behind a Vite `/api` proxy. It states that `os dev` persists to SQLite, that `--fresh` gives a throwaway database, and that the retired in-memory driver is refused. Eval 2 grades `os dev`, `proxy` and `objectstack.config`, and refuses the MSW setup. Net -35 lines in the guide.
13+
- **Console.** `VITE_USE_MOCK_SERVER`, which no source reads, leaves `.env.development`, `.env.production` and the Vercel build command. The `mockServiceWorker.js` rewrite exclusion and the `vendor-msw` chunk row in `docs/deployment.md` go too. No runtime behaviour changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
fix(metadata-protocol): promoting a permission set with no draft answers `NO_DRAFT` before another owner's name collision
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The draft promotion (`POST /api/v1/meta/permission/:name/publish`, and `publishPackageDrafts`, which promotes through the same step) asked the permission-set name judge (`NAMESPACE_CONFLICT` / 422) even when no draft existed at the address, so a publish with nothing to publish, of a name another package holds, answered the collision instead of `NO_DRAFT` / 404. The name judge now runs only when a draft was read, like the promotion's other draft judges, so the caller is told about its own state first. Refusal for refusal: with no draft the promotion was refused before and is refused now, and with a draft the same refusals run in the same order.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(studio): the flow Problems list says what the publish will refuse, and a blank expression reads "This value needs an expression"
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
On the Studio's Automations page, a flow's Problems list judged only the canvas structure, so an Update Records value written as an expression and left blank showed a refusal under the value while Problems said "No problems — this flow is structurally valid", and Publish then refused the flow.
12+
13+
The Problems list now reads the verdict the pending draft's read already carries, the one the step inspectors read and the one Publish enforces, so a value the publish refuses is listed, located by the server's own words naming the step and the field. The value cell and the Problems row say the same thing, in business words: "This value needs an expression. Write one, or switch the value back to text." Any other refusal is shown as before.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(studio): a report's new Order or Blocks item, or a chart type picked before its axes, is held until it is filled in, instead of being autosaved and refused
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
In the Studio's Interfaces pillar, a report's properties used to autosave an unfinished edit: "Add item" under **Order** sent an empty sort key, "Add item" under a joined report's **Blocks** sent an empty block, and picking a **Chart type** before its X and Y axes sent a chart without them. The draft door refused each (422), and every later save of the report carried the same body and was refused too, so an edit made afterwards was lost on reload.
12+
13+
Such a report is now held the way a dashboard's unfinished list item is: nothing is sent while the report's own schema refuses the item, or the chart, only for inputs it leaves unset. A neutral line names what it needs (for example "Order By on item #1 of “Order” needs a value", or "X-Axis (dimension) on the report’s chart needs a value") with a **Show me** that opens the report's properties at that input, and Publish refuses while it is held. Once the input is filled in, the next autosave sends every edit kept meanwhile. An item or a chart refused for a value it holds is not held: it is sent, and the refusal it draws shows as before.

‎packages/metadata-protocol/src/protocol.permission-set-name-cross-package.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,20 @@ describe('the draft promotion refuses a permission-set draft whose name another
524524
]);
525525
});
526526

527+
it('with no draft at the address, the caller is told about its own state first: the repository\'s NO_DRAFT (404), not the collision another owner\'s name would raise', async () => {
528+
const h = makeProtocol();
529+
// B holds the active name; A has no draft of it at all.
530+
await h.protocol.saveMetaItem({ type: 'permission', name: SET, item: perm(SET), packageId: OWNER_B });
531+
h.saveDoor.mockClear();
532+
533+
const { error } = await attempt(() => h.protocol.publishMetaItem({ type: 'permission', name: SET, packageId: OWNER_A }));
534+
535+
expect(error).toBeInstanceOf(Error);
536+
expect(error.code).toBe('NO_DRAFT');
537+
expect(error.status).toBe(404);
538+
expect(storedSets(h)).toEqual([{ row: SET, state: 'active', binding: OWNER_B }]);
539+
});
540+
527541
it('CONTROL: the owning package publishes its own first set, and later re-publishes its own draft of it', async () => {
528542
const h = makeProtocol();
529543
await h.protocol.saveMetaItem({ type: 'permission', name: SET, item: perm(SET), packageId: OWNER_B, mode: 'draft' });

‎packages/metadata-protocol/src/protocol.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23062,7 +23062,10 @@ export class ObjectStackProtocolImplementation implements
2306223062
// binding the active row is written under), before `repo.promoteDraft`
2306323063
// writes — so a batch publish aborts on it as it aborts on the owner
2306423064
// refusal above. See {@link permissionSetNameCollisionRefusal}.
23065-
{
23065+
// Asked only when a draft was read, like its siblings: with none at
23066+
// the address the caller is told `repo.promoteDraft`'s `NO_DRAFT`
23067+
// first, the fact about its own state, rather than another owner's.
23068+
if (draftForGate) {
2306623069
const nameCollision = await this.permissionSetNameCollisionRefusal(
2306723070
singularType, request.name, promoteInto, 'publish',
2306823071
);

‎packages/objectql/src/registry.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3499,6 +3499,11 @@ export class SchemaRegistry {
34993499
* {@link securityCatalogPackageHolders}'s answer, the one predicate. Built-in
35003500
* names are skipped, as the cold boot skips them (see the method above).
35013501
* Sorted by type in that list's order, then name.
3502+
*
3503+
* Also read BY NAME from `@objectstack/metadata-protocol`
3504+
* (`protocol-save-door-refusals.ts`, `registryPackageHoldersOf`): renaming
3505+
* this member means updating that caller and
3506+
* `protocol-permission-set-name-registry-reading.test.ts`.
35023507
*/
35033508
private packageHeldSecurityCatalogNames(): Array<{ catalogType: SecurityCatalogType; name: string; packageIds: string[] }> {
35043509
const held: Array<{ catalogType: SecurityCatalogType; name: string; packageIds: string[] }> = [];

‎skills/objectui/evals/data-integration.json‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,18 @@
2121
},
2222
{
2323
"id": 2,
24-
"prompt": "Set up MSW mock data for my ObjectUI app so I can develop the frontend without a running backend. I have a Todo object with title, completed, and dueDate fields.",
25-
"expected_output": "Provides MSW browser setup using ObjectKernel + InMemoryDriver + MSWPlugin, objectstack.config with the Todo schema, and main.tsx bootstrap pattern.",
24+
"prompt": "I want to build my ObjectUI frontend before the backend is deployed. I have a Todo object with title, completed, and dueDate fields. How do I get a working API locally?",
25+
"expected_output": "Runs the real server locally with os dev (the Todo object in objectstack.config) and proxies /api to it from the Vite dev server; no MSW plugin.",
2626
"files": [],
2727
"assertions": {
2828
"must_contain": [
29-
"ObjectKernel",
30-
"InMemoryDriver",
31-
"MSWPlugin",
29+
"os dev",
30+
"proxy",
3231
"objectstack.config"
3332
],
3433
"must_not_contain": [
35-
"msw rest.get('/api/todos'"
34+
"enableBrowser",
35+
"setupWorker"
3636
]
3737
}
3838
},

‎skills/objectui/guides/data-integration.md‎

Lines changed: 19 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -351,65 +351,33 @@ function MyPlugin() {
351351
}
352352
```
353353

354-
## MSW mock setup (frontend-first development)
354+
## Frontend-first development
355355

356-
For developing the UI without a running backend, use Mock Service Worker with ObjectStack's in-browser kernel.
356+
There is no mock-server plugin: the MSW plugin was removed in 12.0, and npm
357+
still serves its stale 11.x build — ⛔ do not install it. Two supported paths:
357358

358-
### Browser mock server
359+
- **No backend:** publish static values as a scope ("Static data (no backend)" above).
360+
- **Real API, locally:** run `os dev` in the ObjectStack app whose
361+
`objectstack.config.ts` declares your objects. In a project that runs Vite
362+
itself (not `objectui dev`), proxy `/api` to it so page and API share one origin:
359363

360-
```typescript
361-
// src/mocks/browser.ts
362-
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
363-
import { ObjectQLPlugin } from '@objectstack/objectql';
364-
import { InMemoryDriver } from '@objectstack/driver-memory';
365-
import { MSWPlugin } from '@objectstack/plugin-msw';
366-
367-
export async function startMockServer(appConfig: any) {
368-
const kernel = new ObjectKernel({ skipSystemValidation: true });
369-
370-
await kernel.use(new ObjectQLPlugin());
371-
372-
const driver = new InMemoryDriver();
373-
await kernel.use(new DriverPlugin(driver, 'memory'));
374-
await kernel.use(new AppPlugin(appConfig));
375-
await kernel.use(new MSWPlugin({
376-
enableBrowser: true,
377-
baseUrl: '/api/v1',
378-
logRequests: true,
379-
}));
380-
381-
await kernel.bootstrap();
382-
383-
// Seed initial data
384-
if (appConfig.manifest?.data) {
385-
for (const dataset of appConfig.manifest.data) {
386-
for (const record of dataset.records) {
387-
await driver.create(dataset.object, record);
388-
}
389-
}
390-
}
391-
}
364+
```bash
365+
os dev # http://localhost:3000; if busy, the banner names the port taken
366+
os dev --fresh # throwaway database, deleted on exit
392367
```
393368

394-
### App entry with MSW
395-
396369
```typescript
397-
// src/main.tsx
398-
import { startMockServer } from './mocks/browser';
399-
import appConfig from '../objectstack.config';
400-
401-
async function bootstrap() {
402-
if (import.meta.env.DEV) {
403-
await startMockServer(appConfig);
404-
}
405-
406-
const root = ReactDOM.createRoot(document.getElementById('root')!);
407-
root.render(<App />);
408-
}
409-
410-
bootstrap();
370+
// vite.config.ts
371+
export default defineConfig({
372+
server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } } },
373+
});
411374
```
412375

376+
By default `os dev` persists to a SQLite file and seeds a dev admin
377+
(`admin@objectos.ai` / `admin123`) on an empty database. It has no in-memory
378+
driver (`--database-driver memory` is refused); `--database :memory:` is
379+
SQLite's in-memory database. Commands and flags: the `objectstack-platform` skill.
380+
413381
### Test DataSource helper
414382

415383
<!-- os:check -->
@@ -507,8 +475,6 @@ unsubscribe?.();
507475

508476
- Spelling `QueryParams` options without the `$` prefix (`limit`, `filter`, `sort`): the index signature accepts them, nothing reads them, and a dropped `$top` fetches the whole table.
509477
- Importing `fetch` directly in components instead of using DataSource from context.
510-
- Forgetting to await `startMockServer()` before rendering — MSW intercepts aren't ready.
511-
- Mismatched `baseUrl` between MSW plugin and ObjectStackAdapter — requests bypass mocks.
512478
- Not providing `getObjectSchema()` — plugins that need field metadata will fail.
513479
- Passing a DataSource adapter instance where a static object is expected (or vice versa).
514480
- Not handling loading/error states in components that fetch data.

‎ui/apps/console/.env.development‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,3 @@
1717
# Setting VITE_SERVER_URL to an absolute origin still works, but it opts this
1818
# app back out of same-origin and into CORS + the 401 above.
1919
VITE_SERVER_URL=
20-
VITE_USE_MOCK_SERVER=false

0 commit comments

Comments
 (0)