@mosetta/react 0.0.11 → 0.0.12

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/AUTHORING.md ADDED
@@ -0,0 +1,183 @@
1
+ # Writing tiles
2
+
3
+ What a block of a mosetta site is made of, and the rules it lives by. Written for whoever writes one
4
+ — a developer, or an assistant writing a project's own blocks.
5
+
6
+ ## A tile palette
7
+
8
+ A tile palette is a folder: `package.json` (its version), `mosetta.json` (its brand), `tsconfig.json`
9
+ and `tiles/`. A tile is a folder under `tiles/`; its files are its **sides**:
10
+
11
+ ```
12
+ tiles/greeting/greeting.component.tsx what the visitor sees (required)
13
+ tiles/greeting/greeting.settings.ts what its settings are — a schema (strongly advised)
14
+ tiles/greeting/greeting.editor.tsx its settings form in the editor (optional)
15
+ tiles/greeting/greeting.server.ts its server actions (optional)
16
+ tiles/greeting/greeting.icon.svg its icon in the block picker (optional)
17
+ tiles/greeting/greetingCommon.ts what the sides share: types, tokens, constants (any name)
18
+ tiles/greeting/greeting.styles.css its own stylesheet, imported by the component (any name)
19
+ ```
20
+
21
+ A tile's full id is `_ns_<brand>_t_<side>_n_<name>_v_<version>`. A page names blocks by full id, so a
22
+ released version never changes: a change is a new version, and a page takes it when its author
23
+ updates the block. The tile's name is its folder's name: letters and digits, starting with a letter
24
+ (`greeting`, `bookingForm`).
25
+
26
+ ## The component side
27
+
28
+ ```tsx
29
+ import { defineComponentSide, useChildren, useSettings, useStyler } from '@mosetta/react';
30
+ import type { IGreetingSettings } from './greetingCommon';
31
+ import './greeting.styles.css';
32
+
33
+ export default defineComponentSide<IGreetingSettings>({
34
+ Component() {
35
+ const styler = useStyler();
36
+ const { text, tone } = useSettings<IGreetingSettings>();
37
+ return (
38
+ <section {...styler.part('root')} className={`greeting greeting--${tone}`}>
39
+ <h2 {...styler.part('title')}>{text}</h2>
40
+ {useChildren({ max: 'many' })}
41
+ </section>
42
+ );
43
+ },
44
+ defaultSettings() {
45
+ return { text: 'Hello', tone: 'warm' };
46
+ },
47
+ });
48
+ ```
49
+
50
+ - **Settings** are the node's data, filled in the editor. `defaultSettings()` is copied into a node
51
+ when it is placed — the runtime never merges defaults later, so give every field a default, and
52
+ read fields defensively (a node saved by an older version may lack a newer field).
53
+ - **Children**: a block holds other blocks only where it calls `useChildren`. `max` says how many fit
54
+ (`1`, `3`, `'many'`); a named slot (`useChildren({ slot: 'footer', max: 'many' })`) is a second place.
55
+ A block that calls none holds nothing.
56
+ - **Styling**: `useStyler().part('<name>')` lets the page's author restyle a part from the editor
57
+ (declare the parts and accept them in the settings schema with `styles(PARTS)`). Use the theme's
58
+ CSS variables for colours and fonts so the block follows the site's theme. Import your own CSS file;
59
+ the build ships it with the tile.
60
+ - **Translatable text**: `bindable: { text: 'text' }` in the definition lets a setting hold a reference
61
+ to a translation (`{"$ref": "i18n/<key>"}`) instead of the words; `useSettings` gives the words in
62
+ the visitor's language.
63
+ - **Other hooks** (all from `@mosetta/react`): `useRoute`, `useRouter`, `useHref`, `Link` (moves
64
+ without reloading), `useLanguages`, `useTheme`, `useAddToHead`, `useMediaUpload`,
65
+ `useIsPreviewMode`.
66
+ - **Rendering on the server**: pages are rendered on the server first. Do not touch `window`,
67
+ `document` or `localStorage` while rendering or at the top of a module — only in effects and event
68
+ handlers.
69
+
70
+ ## The settings schema
71
+
72
+ ```ts
73
+ // tiles/greeting/greeting.settings.ts
74
+ import { bindable, settingsOf, styles, z } from '@mosetta/settings';
75
+ import { GREETING_PARTS } from './greetingCommon';
76
+ import type { IGreetingSettings } from './greetingCommon';
77
+
78
+ export const settings = settingsOf<IGreetingSettings>()(z.object({
79
+ text: bindable(z.string().describe('the greeting, plain text')),
80
+ tone: z.enum(['warm', 'cool']).describe('the colour behind it'),
81
+ styles: styles(GREETING_PARTS).optional(),
82
+ }).describe('A greeting: a heading on a coloured band, with blocks under it'));
83
+ ```
84
+
85
+ The build checks the schema against the settings type and puts it into the tile's descriptor: the
86
+ editor and assistants read what each setting does and which values it allows, and settings are
87
+ checked where they are written. Describe every field and the block itself in plain words — that text
88
+ is how others learn the block without reading its code.
89
+
90
+ ## The editor side (optional)
91
+
92
+ Without one, a block's settings are edited by its schema. Write one when the form needs more than
93
+ fields:
94
+
95
+ ```tsx
96
+ import { defineEditorSide, useSettingsDraft } from '@mosetta/react';
97
+ import type { IGreetingSettings } from './greetingCommon';
98
+
99
+ export default defineEditorSide<IGreetingSettings>({
100
+ Component() {
101
+ const { draft, update } = useSettingsDraft<IGreetingSettings>();
102
+ return <input value={draft.text} data-field="text" onChange={(e) => update({ text: e.target.value })} />;
103
+ },
104
+ });
105
+ ```
106
+
107
+ `data-field` marks a bindable field: the editor offers a translation reference beside it. An editor
108
+ side may also declare `preview` (a small tree that shows the block in the picker) and `variants`
109
+ (states worth seeing side by side on the block's page).
110
+
111
+ ## Server actions
112
+
113
+ Code that must not run in the browser — reading a database, sending mail, using a secret — is a
114
+ server action. The token is shared by both sides; keep it in the common file:
115
+
116
+ ```ts
117
+ // tiles/booking/bookingCommon.ts
118
+ import { ServerActionToken } from '@mosetta/server-action/core';
119
+
120
+ export interface IBookingInput { name: string; email: string; slot: string }
121
+ export const bookToken = new ServerActionToken<IBookingInput, { ok: boolean }>({ actionName: 'book' });
122
+ ```
123
+
124
+ ```ts
125
+ // tiles/booking/booking.server.ts
126
+ import { DiInject, DiServerAction, defineServerSide, SECRETS_TOKEN, ServerAction } from '@mosetta/server-action/server';
127
+ import type { ICreateSchema, ISecrets, IServerActionPayload } from '@mosetta/server-action/server';
128
+ import { bookToken } from './bookingCommon';
129
+ import type { IBookingInput } from './bookingCommon';
130
+
131
+ @DiServerAction(bookToken)
132
+ class BookAction extends ServerAction<IBookingInput, { ok: boolean }> {
133
+ constructor(@DiInject(SECRETS_TOKEN) private readonly secrets: ISecrets) {
134
+ super();
135
+ }
136
+
137
+ authorize() {
138
+ return true; // a visitor's form: anyone may send it
139
+ }
140
+
141
+ createSchema({ builder }: ICreateSchema) {
142
+ return builder.object({ name: builder.string().min(1), email: builder.string().email(), slot: builder.string() });
143
+ }
144
+
145
+ async action({ input }: IServerActionPayload<IBookingInput>) {
146
+ const token = await this.secrets.get('D1_TOKEN');
147
+ // … write the booking with it
148
+ return { ok: Boolean(token && input.slot) };
149
+ }
150
+ }
151
+
152
+ export default defineServerSide({ services: [BookAction] });
153
+ ```
154
+
155
+ ```tsx
156
+ // in the component side
157
+ import { EServerActionStart } from '@mosetta/server-action/core';
158
+ import { useServerAction } from '@mosetta/react/server-action';
159
+
160
+ const booking = useServerAction({ token: bookToken, start: EServerActionStart.START_MANUAL });
161
+ // booking.start?.(input) sends it; booking.isLoading(), booking.isSuccess(), booking.serverData
162
+ ```
163
+
164
+ - **`authorize` is required.** It says who may call: `true` for anyone, or a check of the `caller` it is
165
+ given (what the host vouches for — on the platform, `roles: ['editor']` for the site's editors).
166
+ - **Input is validated** by `createSchema` before `authorize` and the action run.
167
+ - **Reading data** for a page: `useServerAction({ token, input })` starts as the block renders and
168
+ caches by input; `start: EServerActionStart.START_FROM_SERVER` runs it during server rendering, so
169
+ the page arrives with the data in it.
170
+ - **Secrets** are asked for by name (`UPPER_SNAKE_CASE`) and set in the site's settings; a value is
171
+ never in the code or the environment. The server side of a tile is named like its component
172
+ (`booking.server.ts` beside `booking.component.tsx`).
173
+
174
+ ## Rules of a project's palette on the platform
175
+
176
+ - Its brand is the project's own; it holds the project's tiles (and the packages they import), every
177
+ tile with its integrity — the build writes it. No prefabs: a project's prefabs are its content.
178
+ - It is built over the platform's tiles: the UI kit and the framework come from the platform, not
179
+ from the palette. Name them in `package.json` at the versions the template gives.
180
+ - Types are checked before a release: a type error in the palette's own files stops it, and the
181
+ error comes back as the checker's line.
182
+ - Every release is the next patch version. Pages keep the version they name; update a block on a
183
+ page to take the new one.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @mosetta/react
2
2
 
3
+ ## 0.0.12
4
+
5
+ ### Patch Changes
6
+
7
+ - `@mosetta/react` ships AUTHORING.md: what a tile is made of and the rules it lives by — sides,
8
+ settings and their schema, children and slots, styling, the editor side, server actions with their
9
+ access, inputs and secrets, and what a project's tile palette on a platform may hold. A catalog entry
10
+ (`@mosetta/content`) may name the public base of its tile host, for a block of a host other than the
11
+ site's primary.
12
+ - @mosetta/server@0.0.12
13
+ - @mosetta/core@0.0.12
14
+ - @mosetta/di@0.0.12
15
+ - @mosetta/runtime@0.0.12
16
+ - @mosetta/server-action@0.0.12
17
+
3
18
  ## 0.0.11
4
19
 
5
20
  ### Patch Changes
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @mosetta/react
2
2
 
3
- The React binding of the mosetta protocol: what a tile's sides are written with.
3
+ The React binding of the mosetta protocol: what a tile's sides are written with. The whole of it —
4
+ sides, settings schemas, editor forms, server actions, the rules a palette lives by — is in
5
+ [AUTHORING.md](./AUTHORING.md), shipped with the package.
4
6
 
5
7
  A tile is a folder under `tiles/` whose files are its sides — `tiles/<name>/<name>.<side>.tsx`. The
6
8
  component side is what the visitor sees; it reads the settings a content manager filled in the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mosetta/react",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "React binding of the mosetta protocol: side definers, hooks, blueprint renderer, DI adapter, server-action hook, startAndMount. Subpath exports only (no barrel) — each entry ships as its own package tile; ./context is the internal state-singleton entry the others re-export from.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -13,11 +13,11 @@
13
13
  "dependencies": {
14
14
  "react": "*",
15
15
  "react-dom": "*",
16
- "@mosetta/core": "0.0.11",
17
- "@mosetta/di": "0.0.11",
18
- "@mosetta/runtime": "0.0.11",
19
- "@mosetta/server": "0.0.11",
20
- "@mosetta/server-action": "0.0.11"
16
+ "@mosetta/core": "0.0.12",
17
+ "@mosetta/di": "0.0.12",
18
+ "@mosetta/runtime": "0.0.12",
19
+ "@mosetta/server": "0.0.12",
20
+ "@mosetta/server-action": "0.0.12"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/react": "*",