uniweb 0.89.0 → 0.89.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -8
- package/partials/agents.md +47 -7
- package/src/framework-index.json +5 -5
- package/src/versions.js +37 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.89.
|
|
3
|
+
"version": "0.89.2",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,17 +41,15 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
-
"@uniweb/
|
|
45
|
-
"@uniweb/core": "^0.38.0",
|
|
46
|
-
"@uniweb/kit": "^0.20.0",
|
|
44
|
+
"@uniweb/semantic-parser": "^1.5.1",
|
|
47
45
|
"@uniweb/content-writer": "^0.3.4",
|
|
48
|
-
"@uniweb/
|
|
49
|
-
"@uniweb/
|
|
46
|
+
"@uniweb/core": "^0.38.1",
|
|
47
|
+
"@uniweb/schemas": "^0.13.0"
|
|
50
48
|
},
|
|
51
49
|
"peerDependencies": {
|
|
50
|
+
"@uniweb/semantic-parser": "^1.5.1",
|
|
52
51
|
"@uniweb/content-reader": "^1.2.8",
|
|
53
|
-
"@uniweb/build": "^0.80.3"
|
|
54
|
-
"@uniweb/semantic-parser": "^1.5.1"
|
|
52
|
+
"@uniweb/build": "^0.80.3"
|
|
55
53
|
},
|
|
56
54
|
"peerDependenciesMeta": {
|
|
57
55
|
"@uniweb/build": {
|
package/partials/agents.md
CHANGED
|
@@ -2510,8 +2510,8 @@ const sessions = entity?.hydrated?.items.filter((item) => item.section === 'sess
|
|
|
2510
2510
|
section's fields (`record.brief.name`, not `record.name`), and a record carries **no
|
|
2511
2511
|
items**. Read one entity for its items. Each item carries `section`, its section's name
|
|
2512
2512
|
(the word a write takes), and `id`, which is what the writer takes to update, move or
|
|
2513
|
-
remove it. `scope: 'mine'` lists only the viewer's own records
|
|
2514
|
-
|
|
2513
|
+
remove it. `scope: 'mine'` lists only the viewer's own records; without it, a list also
|
|
2514
|
+
holds what was shared with the viewer.
|
|
2515
2515
|
|
|
2516
2516
|
⭐ **`absent` and an empty `ready` are different answers, and confusing them is the
|
|
2517
2517
|
mistake to avoid.** `absent` = there is no live source (no `backend` service, or nobody signed
|
|
@@ -2543,10 +2543,12 @@ await writer.remove(itemId)
|
|
|
2543
2543
|
|
|
2544
2544
|
### ⛔ Permissions are the SERVER'S, and your UI is only a courtesy
|
|
2545
2545
|
|
|
2546
|
-
Gate your controls on what the viewer may do — `
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2546
|
+
Gate your controls on what the viewer may do — a record's own `can_edit` from a
|
|
2547
|
+
single-entity read — but **never rely on that for safety**. A foundation runs with
|
|
2548
|
+
exactly the viewer's own authority, so hiding a button hides a button. ⛔ **Not on
|
|
2549
|
+
`viewer.roles`:** a session signed in on a site holds a member's rights — the operator's
|
|
2550
|
+
too — so its roles never name an administrator, and a gate on `system_admin` shows the
|
|
2551
|
+
operator nothing.
|
|
2550
2552
|
|
|
2551
2553
|
What protects content is the **entry**: a member writes their own entries and not
|
|
2552
2554
|
another's, and the store enforces it. **Models are open** — anyone signed in may
|
|
@@ -2565,6 +2567,42 @@ sections:
|
|
|
2565
2567
|
`append_only` holds against the item's own author. That is the difference between a
|
|
2566
2568
|
permission model and a CSS one.
|
|
2567
2569
|
|
|
2570
|
+
### The Admin link
|
|
2571
|
+
|
|
2572
|
+
```jsx
|
|
2573
|
+
const { adminHref } = useSession()
|
|
2574
|
+
{adminHref && <a href={adminHref}>Admin</a>}
|
|
2575
|
+
```
|
|
2576
|
+
|
|
2577
|
+
`adminHref` is set only for a signed-in account the backend's administration admits —
|
|
2578
|
+
the operator, and whoever else administers it — and is `null` for everyone else. It
|
|
2579
|
+
takes them there already signed in. ⛔ **Navigate to it, never `fetch` it**: the answer is
|
|
2580
|
+
a redirect carrying a one-time credential no script on the site is to hold. It decides
|
|
2581
|
+
what to offer, not what is allowed — on the site the operator acts as a member.
|
|
2582
|
+
|
|
2583
|
+
### The links the backend mails
|
|
2584
|
+
|
|
2585
|
+
A sign-up's confirmation, a password reset and an invitation to an address with no
|
|
2586
|
+
account all arrive as a link to the site's root, a token in the query — `?verify=`,
|
|
2587
|
+
`?reset=`, `?offer=` — and the page it lands on finishes the step:
|
|
2588
|
+
|
|
2589
|
+
```jsx
|
|
2590
|
+
const { step, status, result, error, confirm } = useMailedLink()
|
|
2591
|
+
// step: null | 'verify' | 'reset' | 'offer' status: 'idle' | 'working' | 'done' | 'error'
|
|
2592
|
+
```
|
|
2593
|
+
|
|
2594
|
+
- `verify` finishes by itself; then the member signs in.
|
|
2595
|
+
- `reset` waits for `confirm({ new_password })`, and the hook supplies the token. A
|
|
2596
|
+
password the rule refuses is `error.extensions.reason === 'password_too_short'`, and
|
|
2597
|
+
the same link works again.
|
|
2598
|
+
- `offer` waits for a sign-in, then claims by itself. `result` is counts,
|
|
2599
|
+
`{ invitations, grants }`, never names. ⛔ `error.kind === 'absent'` means unknown,
|
|
2600
|
+
revoked or already used, alike: one sentence, no cause.
|
|
2601
|
+
|
|
2602
|
+
Render it where the root renders it — a header or a layout is on every page. ⛔ Never
|
|
2603
|
+
copy the token anywhere: it is a credential, it stays in the URL until the backend has
|
|
2604
|
+
judged it, and then the hook takes it off.
|
|
2605
|
+
|
|
2568
2606
|
### A backend on your machine
|
|
2569
2607
|
|
|
2570
2608
|
You do not need a live backend to build against one. In `site.yml`:
|
|
@@ -2586,7 +2624,9 @@ supplies on its own origin, so cookies behave exactly as they will in production
|
|
|
2586
2624
|
no address of your own for it: under `services:` an address means a backend you run, and
|
|
2587
2625
|
asks your host to leave its own off. It **enforces** who
|
|
2588
2626
|
may edit an entry and `append_only`, so a permission you are relying on fails on your
|
|
2589
|
-
machine rather than in front of a user.
|
|
2627
|
+
machine rather than in front of a user. The mail it would send is printed in the dev
|
|
2628
|
+
server's terminal, its link on your site's root — follow it to try your page; seed
|
|
2629
|
+
`offers: [{ email }]` to try an invitation. State is in memory; restart to reset.
|
|
2590
2630
|
|
|
2591
2631
|
`uniweb create my-event --template conference` is a worked example of all of this.
|
|
2592
2632
|
|
package/src/framework-index.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-11T16:47:25.397Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/api": {
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.9.0"
|
|
7
7
|
},
|
|
8
8
|
"@uniweb/build": {
|
|
9
9
|
"version": "0.80.3"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"version": "0.3.4"
|
|
16
16
|
},
|
|
17
17
|
"@uniweb/core": {
|
|
18
|
-
"version": "0.38.
|
|
18
|
+
"version": "0.38.1"
|
|
19
19
|
},
|
|
20
20
|
"@uniweb/frame-bridge": {
|
|
21
21
|
"version": "0.3.2"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"version": "0.7.15"
|
|
37
37
|
},
|
|
38
38
|
"@uniweb/runtime": {
|
|
39
|
-
"version": "0.29.
|
|
39
|
+
"version": "0.29.6"
|
|
40
40
|
},
|
|
41
41
|
"@uniweb/scene": {
|
|
42
42
|
"version": "0.1.3"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"version": "0.1.2"
|
|
55
55
|
},
|
|
56
56
|
"@uniweb/templates": {
|
|
57
|
-
"version": "0.18.
|
|
57
|
+
"version": "0.18.8"
|
|
58
58
|
},
|
|
59
59
|
"@uniweb/theming": {
|
|
60
60
|
"version": "0.2.0"
|
package/src/versions.js
CHANGED
|
@@ -9,26 +9,27 @@
|
|
|
9
9
|
*
|
|
10
10
|
* ## How versions are resolved
|
|
11
11
|
*
|
|
12
|
-
* 1. Start from the CLI's own `package.json` dependencies.
|
|
13
|
-
*
|
|
14
|
-
* spec
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
12
|
+
* 1. Start from the CLI's own `package.json` dependencies. A published CLI
|
|
13
|
+
* carries concrete ranges like `^0.9.1` there — pnpm rewrites each
|
|
14
|
+
* `workspace:^` spec at publish.
|
|
15
|
+
* 2. For any dep that is still a `workspace:` spec (the CLI is running
|
|
16
|
+
* *from* a workspace checkout — local dev, sandboxes, tests), read the
|
|
17
|
+
* `package.json` of the sibling package under `framework/<name>/` and
|
|
18
|
+
* use `^<that version>`: the range a published CLI would carry.
|
|
19
|
+
* 3. Scan `framework/*` for every other `@uniweb/*` package (press, loom,
|
|
20
|
+
* scholar, etc.), so a new package reaches the scaffolder automatically.
|
|
21
|
+
* 4. A published CLI has no `framework/` beside it: it reads every other
|
|
22
|
+
* package's version from its bundled `framework-index.json`, refreshed
|
|
23
|
+
* when the CLI is published.
|
|
24
|
+
*
|
|
25
|
+
* ⭐ A version is not a reason to depend on a package. Steps 3 and 4 name
|
|
26
|
+
* every `@uniweb/*` package, so the CLI's `dependencies` list only what its
|
|
27
|
+
* code loads — each one is installed with every `npx uniweb`.
|
|
28
|
+
*
|
|
29
|
+
* The previous implementation shipped a hardcoded fallback table that
|
|
30
|
+
* nothing kept in sync, so every scaffold silently pinned years-old
|
|
31
|
+
* versions. That table is gone. A package none of these steps resolves gets
|
|
32
|
+
* the `{{version}}` helper's `^0.1.0` (templates/processor.js).
|
|
32
33
|
*/
|
|
33
34
|
|
|
34
35
|
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
@@ -198,16 +199,17 @@ function getFrameworkRoot() {
|
|
|
198
199
|
|
|
199
200
|
/**
|
|
200
201
|
* Read the current on-disk version of a specific `@uniweb/*` package by
|
|
201
|
-
* looking up `framework/<last-segment>/package.json
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
202
|
+
* looking up `framework/<last-segment>/package.json`, as a caret range
|
|
203
|
+
* (e.g. `^0.7.11`) — or null if the package isn't on disk (the CLI is
|
|
204
|
+
* running from npm, not from a workspace checkout).
|
|
205
|
+
*
|
|
206
|
+
* ⭐ A caret, because that is what a published CLI carries: pnpm rewrites
|
|
207
|
+
* its `workspace:^` deps to `^<version>` at publish. So `uniweb update` and
|
|
208
|
+
* the scaffolder write the same `package.json` whether the CLI ran from npm
|
|
209
|
+
* or from a workspace. ⛔ This returned the version exact until 2026-10-11:
|
|
210
|
+
* right while the deps were `workspace:*`, which pnpm publishes exact, and
|
|
211
|
+
* wrong once they became `workspace:^` — so a scaffold from a workspace
|
|
212
|
+
* checkout pinned `0.20.0` where a user's got `^0.20.0`.
|
|
211
213
|
*/
|
|
212
214
|
function readWorkspaceVersion(packageName) {
|
|
213
215
|
const root = getFrameworkRoot()
|
|
@@ -219,7 +221,7 @@ function readWorkspaceVersion(packageName) {
|
|
|
219
221
|
try {
|
|
220
222
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
221
223
|
if (pkg.name === packageName && pkg.version) {
|
|
222
|
-
return pkg.version
|
|
224
|
+
return `^${pkg.version}`
|
|
223
225
|
}
|
|
224
226
|
} catch {}
|
|
225
227
|
return null
|
|
@@ -310,8 +312,8 @@ function discoverWorkspacePackages() {
|
|
|
310
312
|
|
|
311
313
|
/**
|
|
312
314
|
* Resolve a single version spec to something npm can install. A concrete
|
|
313
|
-
* spec like `^0.9.0` passes through untouched; `workspace
|
|
314
|
-
*
|
|
315
|
+
* spec like `^0.9.0` passes through untouched; a `workspace:` spec becomes
|
|
316
|
+
* `^<on-disk version>` of the named package; anything still unresolved
|
|
315
317
|
* returns null so the caller can fall back.
|
|
316
318
|
*/
|
|
317
319
|
function resolveVersionSpec(spec, packageName) {
|
|
@@ -328,8 +330,8 @@ function resolveVersionSpec(spec, packageName) {
|
|
|
328
330
|
* Priority (highest first):
|
|
329
331
|
*
|
|
330
332
|
* 1. A concrete version spec already in the CLI's own `package.json`
|
|
331
|
-
* (the state after an npm publish: `workspace
|
|
332
|
-
*
|
|
333
|
+
* (the state after an npm publish: pnpm rewrites `workspace:^` to
|
|
334
|
+
* `^<version>`), or a `workspace:` spec read as `^<on-disk version>`.
|
|
333
335
|
* 2. Live workspace walk — every `@uniweb/*` package found under
|
|
334
336
|
* `framework/*` at CLI invocation time. This is the path that
|
|
335
337
|
* matters for local dev: a freshly-added package becomes
|