uniweb 1.7.1 → 1.7.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,15 +41,15 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
+ "@uniweb/content-writer": "~1.1.2",
44
45
  "@uniweb/core": "~1.2.2",
45
46
  "@uniweb/semantic-parser": "~1.8.1",
46
- "@uniweb/schemas": "~1.3.0",
47
- "@uniweb/content-writer": "~1.1.2"
47
+ "@uniweb/schemas": "~1.3.1"
48
48
  },
49
49
  "peerDependencies": {
50
- "@uniweb/semantic-parser": "~1.8.1",
50
+ "@uniweb/build": "~1.3.7",
51
51
  "@uniweb/content-reader": "~1.4.0",
52
- "@uniweb/build": "~1.3.6"
52
+ "@uniweb/semantic-parser": "~1.8.1"
53
53
  },
54
54
  "peerDependenciesMeta": {
55
55
  "@uniweb/build": {
@@ -2517,16 +2517,17 @@ component that never needed one.
2517
2517
  ### Reading
2518
2518
 
2519
2519
  ```jsx
2520
- const { status, records } = useRecords({ schema: '@acme/track' }) // summaries: record.brief, no items
2521
- const { entity } = useEntity({ schema: '@acme/track', uuid: record.uuid })
2520
+ const { status, records } = useRecords({ schema: '@/track' }) // summaries: record.brief, no items
2521
+ const { entity } = useEntity({ schema: '@/track', uuid: record.uuid })
2522
2522
  const sessions = entity?.hydrated?.items.filter((item) => item.section === 'sessions')
2523
2523
  ```
2524
2524
 
2525
- ⛔ **Name a type by its full name, `@scope/name`** — the scope your foundation registered
2526
- under (`@acme/track`). `@/track` is right in `meta.js` and `queries.yml`, where the build and
2527
- `push` qualify it for you, but a `schema` you hand `@uniweb/api` goes to the backend as written,
2528
- and the backend matches the exact name — so the package refuses `@/track` before sending it
2529
- (`kind: 'invalid'`), and its mock refuses a seed that names one.
2525
+ ⭐ **Name your foundation's own types `@/track`**, as `meta.js` and `queries.yml` do. A built
2526
+ foundation sends one in the scope in its name — `@acme/track` for `@acme/marketing`, the name
2527
+ `register` stored it under — and `uniweb dev` sends it as written, which is how your mock's
2528
+ seed names it. Name any other type in full (`@std/person`). Real-backend testing is a job for a
2529
+ built foundation. Outside a Uniweb build nothing knows your scope, so `@/track` is refused
2530
+ before anything is sent (`kind: 'invalid'`).
2530
2531
 
2531
2532
  ⛔ **What a member wrote is text.** Render a field as `{record.brief.name}` or `<PlainText text={…} />` — never `<P text>`, `<H1>`–`<H6>` or `SafeHtml`, which render HTML: a member who typed `<img src=x onerror=…>` would run it as whoever views the page. A rich-text field (a ProseMirror document) renders through kit's `Render`, which escapes its text.
2532
2533
 
@@ -2546,7 +2547,7 @@ for the first tells a visitor their content is gone when it was never requested.
2546
2547
  ### Writing
2547
2548
 
2548
2549
  ```jsx
2549
- const writer = useEntityWriter({ schema: '@acme/track', uuid: track.uuid })
2550
+ const writer = useEntityWriter({ schema: '@/track', uuid: track.uuid })
2550
2551
 
2551
2552
  await writer.create({ title: 'Keynote' }, { section: 'sessions', position: 'last' })
2552
2553
  await writer.update(itemId, { ...item.data, room: 'Hall A' }) // whole-data replace
@@ -2693,7 +2694,7 @@ it, and takes it back. An instructor enrolling students in a course is this; nam
2693
2694
  your solution does ("enrolled", "course staff"):
2694
2695
 
2695
2696
  ```jsx
2696
- const { status, grants, grant, revoke, working } = useAccess({ schema: '@acme/course', uuid: course.uuid })
2697
+ const { status, grants, grant, revoke, working } = useAccess({ schema: '@/course', uuid: course.uuid })
2697
2698
  // status 'ready' for whoever administers it; 'forbidden' for everyone else — draw nothing
2698
2699
  // grants: [{ account_id, permission, account: { handle, uuid, … }, … }]
2699
2700
  await grant({ handle: 'sam' }, 'read') // or an account id: a candidate, a grant's account, an owner_id
@@ -2717,7 +2718,7 @@ viewer is placed in:
2717
2718
 
2718
2719
  ```jsx
2719
2720
  const { units } = useUnits() // [{ unit, id, handle?, name?, parent?, depth, primary }]
2720
- await createEntity({ schema: '@acme/submission', unit: course.unit, items: [...] })
2721
+ await createEntity({ schema: '@/submission', unit: course.unit, items: [...] })
2721
2722
  ```
2722
2723
 
2723
2724
  Landing decides where an entry is, not who may open it.
@@ -2748,7 +2749,8 @@ server's terminal, its link on your site's root — follow it to try your page.
2748
2749
  `offers: [{ email, invitations: 1 }]` to try an invitation: mailed to an address no
2749
2750
  account holds, or waiting at once for the seeded account whose address it is. A seeded
2750
2751
  entity takes `grants: { member: 'read' }`, and `units: [{ handle, parent?, members, floor? }]`
2751
- gives a create somewhere to land. State is in memory; restart to reset.
2752
+ gives a create somewhere to land. A seed names your own types `@/track`, as `uniweb dev` sends
2753
+ them, and any other in full; it matches a name exactly. State is in memory; restart to reset.
2752
2754
 
2753
2755
  `uniweb create my-event --template conference` is a worked example of all of this.
2754
2756
 
@@ -2974,7 +2976,7 @@ uniweb build # merges translations, emits dist/es/, dist/fr/
2974
2976
 
2975
2977
  Running `extract` before a build is the usual first mistake — it reads the compiled content, so a stale build yields a stale manifest. After content changes, `uniweb i18n sync` updates the manifest and `init` refreshes the language files; `status` and `audit` report coverage and stale entries.
2976
2978
 
2977
- **A value is inline Markdown.** A sentence's links and bold go into its translation — `"Voir [Rôles](page:docs/roles)."` — or it renders without them, and coverage still counts it; `uniweb i18n status` lists these under *Loses markup*. A **button** — a line that is only a link — is the exception: translate its label alone and it keeps its link and options (`{role=primary}`). Buttons on consecutive lines are one string; give one label per line (`"Créer un groupe\nEn savoir plus"`). A link belongs to the place whose source has one: the same words are one string wherever they appear, so where they appear without a link — a heading, a page title, a data block's plain string — the translation's link is left off and its words kept.
2979
+ **A value is inline Markdown.** A sentence's links and bold go into its translation — `"Voir [Rôles](page:docs/roles)."` — or it renders without them, and coverage still counts it; `uniweb i18n status` lists these under *Loses markup*. A **button** — a line that is only a link — is the exception: translate its label alone and it keeps its link and options (`{role=primary}`). Buttons on consecutive lines are one string; give one label per line (`"Créer un groupe\nEn savoir plus"`). The same words are one string wherever they appear: where their places differ — a heading here, a link there — the manifest marks it `markupVaries`, `init` seeds the plain words, and the words alone are what to write: a link or button keeps its own target, and a link written into the value goes to every place the string appears.
2978
2980
 
2979
2981
  **Two mechanisms.** *Hash-based strings* (the default) — `locales/{locale}.json` maps content hash → translation, so structure stays identical across languages. When one source string needs different translations depending on where it appears, the value takes an override form:
2980
2982
 
@@ -384,12 +384,23 @@ async function runExtract(siteRoot, config, args) {
384
384
  * translation value as inline markdown, so the value channel was lossless the whole
385
385
  * time — only the prompt was lossy. Measured 2026-09-18, @uniweb/build 0.44.4.
386
386
  *
387
+ * ⭐ Except where the unit's places disagree on markup (`markupVaries`, written by
388
+ * `@uniweb/build`'s extraction): one value serves every place, so such a unit starts
389
+ * with its plain words — which a plain place takes as they are, and a link or a button
390
+ * takes as its label, keeping its own target. ⛔ Until 2026-10-16 it started with the
391
+ * markup of whichever place had some, so an untouched file turned a header's heading
392
+ * into a link. A manifest from an extraction before that has no `markupVaries`, and
393
+ * seeds as it did.
394
+ *
387
395
  * ⚠️ This fills MISSING keys only. A locale file already seeded from the flat source
388
396
  * is not repaired by re-running; use `uniweb i18n generate <locale> --force` against
389
397
  * a freshly extracted manifest to get the markup-bearing source back.
398
+ *
399
+ * @param {{ source: string, markup?: string, markupVaries?: boolean }} unit - a manifest unit
400
+ * @returns {string}
390
401
  */
391
- function starterValue(unit) {
392
- return unit.markup || unit.source
402
+ export function starterValue(unit) {
403
+ return unit.markupVaries ? unit.source : unit.markup || unit.source
393
404
  }
394
405
 
395
406
  /**
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-10-17T02:22:18.325Z",
3
+ "generatedAt": "2026-10-17T13:42:01.369Z",
4
4
  "packages": {
5
5
  "@uniweb/api": {
6
- "version": "1.3.0"
6
+ "version": "1.4.0"
7
7
  },
8
8
  "@uniweb/build": {
9
- "version": "1.3.6"
9
+ "version": "1.3.7"
10
10
  },
11
11
  "@uniweb/content-reader": {
12
12
  "version": "1.4.0"
@@ -42,7 +42,7 @@
42
42
  "version": "1.0.1"
43
43
  },
44
44
  "@uniweb/schemas": {
45
- "version": "1.3.0"
45
+ "version": "1.3.1"
46
46
  },
47
47
  "@uniweb/scholar": {
48
48
  "version": "1.1.0"