@jarenjs/studio 0.34.0
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/README.md +72 -0
- package/dist/types/assemble.d.ts +67 -0
- package/dist/types/component/editor.d.ts +68 -0
- package/dist/types/component/host.d.ts +45 -0
- package/dist/types/component/index.d.ts +262 -0
- package/dist/types/component/view.d.ts +232 -0
- package/dist/types/component/viewmodel.d.ts +22 -0
- package/dist/types/errors.d.ts +31 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/project.d.ts +31 -0
- package/dist/types/validate.d.ts +48 -0
- package/docs/PROJECT-FORMAT.md +112 -0
- package/package.json +69 -0
- package/schemas/jaren-project.draft-07.schema.json +41 -0
- package/schemas/jaren-project.schema.json +47 -0
- package/src/assemble.js +127 -0
- package/src/component/editor.js +59 -0
- package/src/component/host.js +53 -0
- package/src/component/index.js +47 -0
- package/src/component/view.js +198 -0
- package/src/component/viewmodel.js +150 -0
- package/src/errors.js +33 -0
- package/src/index.js +20 -0
- package/src/project.js +80 -0
- package/src/validate.js +191 -0
- package/styles/studio.css +227 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The IDE shell as a JSLT view — the chrome is a document, rendered
|
|
3
|
+
* by the same engine as the rest of the suite (no imperative chrome, no
|
|
4
|
+
* syntax highlighting). Following the suite's rule convention: one mode
|
|
5
|
+
* (`project`), rules matched by their ABSOLUTE slice path, `$apply` and
|
|
6
|
+
* body references RELATIVE to the matched node. The host mounts the view
|
|
7
|
+
* model at `$.ui.project`.
|
|
8
|
+
*
|
|
9
|
+
* The stage's live nested app and the drag splitter are the only
|
|
10
|
+
* imperative islands — `jaren-widget`s the host registers. The layout
|
|
11
|
+
* mode rides a `data-mode` attribute so the grid switches in CSS with no
|
|
12
|
+
* computed class; the phone pane rides `data-pane` the same way, and a
|
|
13
|
+
* kind badge rides `data-badge`.
|
|
14
|
+
*/
|
|
15
|
+
/** The one mode this view uses. */
|
|
16
|
+
export declare const PROJECT_MODE = "project";
|
|
17
|
+
/** The slice the host mounts the view model at. */
|
|
18
|
+
export declare const PROJECT_BASE = "$.ui.project";
|
|
19
|
+
/** The modes the host merges into the site stylesheet. */
|
|
20
|
+
export declare const projectModes: Readonly<{
|
|
21
|
+
project: {
|
|
22
|
+
unmatched: string;
|
|
23
|
+
};
|
|
24
|
+
}>;
|
|
25
|
+
/** The studio's JSLT rules — spread into the site stylesheet. */
|
|
26
|
+
export declare const projectRules: ({
|
|
27
|
+
match: string;
|
|
28
|
+
mode: string;
|
|
29
|
+
body: (string | (string | {
|
|
30
|
+
name: string;
|
|
31
|
+
class: string;
|
|
32
|
+
role: string;
|
|
33
|
+
'aria-orientation': string;
|
|
34
|
+
'aria-label': string;
|
|
35
|
+
'aria-valuemin': string;
|
|
36
|
+
'aria-valuemax': string;
|
|
37
|
+
'aria-valuenow': string;
|
|
38
|
+
tabindex: string;
|
|
39
|
+
props: {
|
|
40
|
+
ratio: string;
|
|
41
|
+
mode: string;
|
|
42
|
+
};
|
|
43
|
+
})[] | (string | any[] | {
|
|
44
|
+
class: string;
|
|
45
|
+
role: string;
|
|
46
|
+
'aria-label': string;
|
|
47
|
+
})[] | (string | {
|
|
48
|
+
$apply: string;
|
|
49
|
+
}[] | (string | (string | {
|
|
50
|
+
value: string;
|
|
51
|
+
})[] | {
|
|
52
|
+
class: string;
|
|
53
|
+
'aria-label': string;
|
|
54
|
+
value: string;
|
|
55
|
+
on: {
|
|
56
|
+
change: string;
|
|
57
|
+
};
|
|
58
|
+
})[] | {
|
|
59
|
+
class: string;
|
|
60
|
+
'aria-label': string;
|
|
61
|
+
})[] | (string | (string | {
|
|
62
|
+
class: string;
|
|
63
|
+
})[] | {
|
|
64
|
+
class: string;
|
|
65
|
+
$if?: undefined;
|
|
66
|
+
} | {
|
|
67
|
+
class?: undefined;
|
|
68
|
+
$if: ((string | (string | {
|
|
69
|
+
name: string;
|
|
70
|
+
props: string;
|
|
71
|
+
})[] | {
|
|
72
|
+
class: string;
|
|
73
|
+
})[] | {
|
|
74
|
+
$if?: undefined;
|
|
75
|
+
$eq: string[];
|
|
76
|
+
} | {
|
|
77
|
+
$if: ({
|
|
78
|
+
$if?: undefined;
|
|
79
|
+
$eq: string[];
|
|
80
|
+
} | {
|
|
81
|
+
$eq?: undefined;
|
|
82
|
+
$if: (string | (string | {
|
|
83
|
+
$apply: string[];
|
|
84
|
+
}[] | {
|
|
85
|
+
class: string;
|
|
86
|
+
})[])[];
|
|
87
|
+
} | {
|
|
88
|
+
$eq?: undefined;
|
|
89
|
+
$if: ((string | {
|
|
90
|
+
class: string;
|
|
91
|
+
})[] | {
|
|
92
|
+
$eq: string[];
|
|
93
|
+
})[];
|
|
94
|
+
})[];
|
|
95
|
+
$eq?: undefined;
|
|
96
|
+
})[];
|
|
97
|
+
})[] | (string | (string | {
|
|
98
|
+
class: string;
|
|
99
|
+
type: string;
|
|
100
|
+
on: {
|
|
101
|
+
click: string;
|
|
102
|
+
};
|
|
103
|
+
})[] | (string | any[] | {
|
|
104
|
+
class: string;
|
|
105
|
+
role: string;
|
|
106
|
+
'aria-label': string;
|
|
107
|
+
})[] | (string | {
|
|
108
|
+
$apply: string;
|
|
109
|
+
}[] | (string | {
|
|
110
|
+
class: string;
|
|
111
|
+
})[] | {
|
|
112
|
+
class: string;
|
|
113
|
+
})[] | {
|
|
114
|
+
class: string;
|
|
115
|
+
})[] | (string | (string | {
|
|
116
|
+
class: string;
|
|
117
|
+
rows: number;
|
|
118
|
+
spellcheck: string;
|
|
119
|
+
autocapitalize: string;
|
|
120
|
+
autocorrect: string;
|
|
121
|
+
autocomplete: string;
|
|
122
|
+
value: any;
|
|
123
|
+
readonly?: string | undefined;
|
|
124
|
+
on: {
|
|
125
|
+
change: string;
|
|
126
|
+
input?: undefined;
|
|
127
|
+
} | {
|
|
128
|
+
input: string;
|
|
129
|
+
change: string;
|
|
130
|
+
};
|
|
131
|
+
})[] | (string | (string | {
|
|
132
|
+
class: string;
|
|
133
|
+
value: string;
|
|
134
|
+
spellcheck: string;
|
|
135
|
+
autocapitalize: string;
|
|
136
|
+
autocomplete: string;
|
|
137
|
+
'aria-label': string;
|
|
138
|
+
on: {
|
|
139
|
+
input: string;
|
|
140
|
+
change: string;
|
|
141
|
+
};
|
|
142
|
+
})[] | (string | string[] | {
|
|
143
|
+
class: string;
|
|
144
|
+
})[] | {
|
|
145
|
+
class: string;
|
|
146
|
+
})[] | {
|
|
147
|
+
class: string;
|
|
148
|
+
$if?: undefined;
|
|
149
|
+
} | {
|
|
150
|
+
$if: (string | (string | {}[] | (string | {
|
|
151
|
+
type: string;
|
|
152
|
+
class: string;
|
|
153
|
+
title: string;
|
|
154
|
+
on: {
|
|
155
|
+
click: string;
|
|
156
|
+
};
|
|
157
|
+
})[] | {
|
|
158
|
+
class: string;
|
|
159
|
+
role: string;
|
|
160
|
+
})[])[];
|
|
161
|
+
class?: undefined;
|
|
162
|
+
} | {
|
|
163
|
+
$if: (string | (string | {
|
|
164
|
+
$apply: string;
|
|
165
|
+
}[] | {
|
|
166
|
+
class: string;
|
|
167
|
+
role: string;
|
|
168
|
+
})[])[];
|
|
169
|
+
class?: undefined;
|
|
170
|
+
})[] | {
|
|
171
|
+
class: string;
|
|
172
|
+
'data-mode': string;
|
|
173
|
+
'data-pane': string;
|
|
174
|
+
})[];
|
|
175
|
+
} | {
|
|
176
|
+
match: string;
|
|
177
|
+
mode: string;
|
|
178
|
+
body: (string | (string | {
|
|
179
|
+
type: string;
|
|
180
|
+
class: string;
|
|
181
|
+
title: string;
|
|
182
|
+
'aria-label': string;
|
|
183
|
+
on: {
|
|
184
|
+
click: {
|
|
185
|
+
action: string;
|
|
186
|
+
with: string;
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
})[] | (string | (string | {
|
|
190
|
+
class: string;
|
|
191
|
+
'data-badge': string;
|
|
192
|
+
})[] | (string | {
|
|
193
|
+
class: string;
|
|
194
|
+
})[] | {
|
|
195
|
+
$if?: undefined;
|
|
196
|
+
type: string;
|
|
197
|
+
class: {
|
|
198
|
+
$if: string[];
|
|
199
|
+
};
|
|
200
|
+
title: string;
|
|
201
|
+
on: {
|
|
202
|
+
click: {
|
|
203
|
+
action: string;
|
|
204
|
+
with: string;
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
} | {
|
|
208
|
+
class?: undefined;
|
|
209
|
+
type?: undefined;
|
|
210
|
+
title?: undefined;
|
|
211
|
+
on?: undefined;
|
|
212
|
+
$if: (string | (string | {
|
|
213
|
+
class: string;
|
|
214
|
+
title: string;
|
|
215
|
+
})[])[];
|
|
216
|
+
})[] | {
|
|
217
|
+
class: string;
|
|
218
|
+
})[];
|
|
219
|
+
} | {
|
|
220
|
+
match: string;
|
|
221
|
+
mode: string;
|
|
222
|
+
body: (string | {}[] | {
|
|
223
|
+
class: string;
|
|
224
|
+
type: string;
|
|
225
|
+
on: {
|
|
226
|
+
click: {
|
|
227
|
+
action: string;
|
|
228
|
+
with: string;
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
})[];
|
|
232
|
+
})[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `projectViewModel(state)` — the IDE's derivation boundary. Given
|
|
3
|
+
* the `state.project` slice it derives everything the JSLT shell renders:
|
|
4
|
+
* the file rail (from the engine's `describe`), the active file's editor
|
|
5
|
+
* value and its coded errors, the docked error strip across every file,
|
|
6
|
+
* and the stage — an assembled app document to mount, a run result to
|
|
7
|
+
* show, or an inert note for a kind that has no editor of its own yet.
|
|
8
|
+
* Pure: nothing here is stored back in state.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Derive the IDE view model from `state.project`.
|
|
12
|
+
* @param {{ project: any }} state - the site state carrying the `project` slice
|
|
13
|
+
* @param {{ operators?: { toOptions: () => any } }} [options]
|
|
14
|
+
* @returns {any}
|
|
15
|
+
*/
|
|
16
|
+
export declare function projectViewModel(state: {
|
|
17
|
+
project: any;
|
|
18
|
+
}, options?: {
|
|
19
|
+
operators?: {
|
|
20
|
+
toOptions: () => any;
|
|
21
|
+
};
|
|
22
|
+
}): any;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Coded errors for `@jarenjs/studio`, on `@jarenjs/core`'s coded
|
|
3
|
+
* contract: a stable `code` (JS0xxx compile/parse time), a bare
|
|
4
|
+
* `reason`, and — where a position in the project exists — a `docPath`.
|
|
5
|
+
*
|
|
6
|
+
* Only the ENVELOPE raises these: a malformed project document, or a
|
|
7
|
+
* duplicate file name. A single FILE's grammar problem is never thrown —
|
|
8
|
+
* it is REPORTED by {@link module:validate.validateFile} as the file
|
|
9
|
+
* kind's own coded errors (`JQ`/`JA`/`JD`…) with their docPaths, so the
|
|
10
|
+
* IDE can show them without stopping the world.
|
|
11
|
+
*/
|
|
12
|
+
import { CodedError } from '@jarenjs/core/errors';
|
|
13
|
+
/** The code table (kept in sync with docs/PROJECT-FORMAT.md). */
|
|
14
|
+
export declare const STUDIO_CODES: Readonly<{
|
|
15
|
+
JS0001: "the project document is invalid";
|
|
16
|
+
JS0002: "a file name is duplicated in the project";
|
|
17
|
+
}>;
|
|
18
|
+
/** A studio-envelope error. */
|
|
19
|
+
export declare class StudioError extends CodedError {
|
|
20
|
+
/**
|
|
21
|
+
* @param {keyof typeof STUDIO_CODES} code
|
|
22
|
+
* @param {string} reason
|
|
23
|
+
* @param {string | { docPath?: string }} [location]
|
|
24
|
+
* @param {{ cause?: unknown }} [options]
|
|
25
|
+
*/
|
|
26
|
+
constructor(code: keyof typeof STUDIO_CODES, reason: string, location?: string | {
|
|
27
|
+
docPath?: string;
|
|
28
|
+
}, options?: {
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `@jarenjs/studio` — the ENGINE (part one of the two-layer
|
|
3
|
+
* package). Headless: it parses a `jaren-project` document, validates
|
|
4
|
+
* each file against its own kind grammar, assembles the runnable
|
|
5
|
+
* artifacts, and classifies a change as structural vs. state-only. It
|
|
6
|
+
* knows the suite's grammars (validate/json/app/flow/db) but nothing of
|
|
7
|
+
* the DOM, `@jarenjs/view` or `@jarenjs/app`'s runtime — the component
|
|
8
|
+
* layer (`./component`) imports the engine, never the reverse.
|
|
9
|
+
*
|
|
10
|
+
* The project is a THIN envelope over typed files; there is deliberately
|
|
11
|
+
* no single composed meta-schema, so a data file may use host-registered
|
|
12
|
+
* operators the closed grammars forbid — the per-file validators are the
|
|
13
|
+
* honest boundary.
|
|
14
|
+
*/
|
|
15
|
+
export { KINDS, LAYOUT_DEFAULT, parseProject, fileOf } from './project.js';
|
|
16
|
+
export { validateFile } from './validate.js';
|
|
17
|
+
export { assembleArtifacts, classifyChange, describe } from './assemble.js';
|
|
18
|
+
export { STUDIO_CODES, StudioError } from './errors.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The project document model: the closed file-kind vocabulary, the
|
|
3
|
+
* frozen IDE `layout` shape, and `parseProject` — validate a candidate
|
|
4
|
+
* against the `jaren-project` envelope schema, reject duplicate file
|
|
5
|
+
* names, and return a NORMALIZED, frozen project (layout defaulted,
|
|
6
|
+
* `active` resolved to a real file). Only the ENVELOPE is gated here; a
|
|
7
|
+
* file's `text` is a string until its kind validator runs (`validate.js`).
|
|
8
|
+
*/
|
|
9
|
+
/** The closed set of file kinds (matches the schema `kind` enum). */
|
|
10
|
+
export declare const KINDS: readonly string[];
|
|
11
|
+
/** The default IDE layout — the frozen `{ mode, ratio, autorun }` shape
|
|
12
|
+
* that rides the share link and the eject, so it must not drift. */
|
|
13
|
+
export declare const LAYOUT_DEFAULT: Readonly<{
|
|
14
|
+
mode: "classic";
|
|
15
|
+
ratio: 0.5;
|
|
16
|
+
autorun: true;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Parse and normalize a project: JSON text or an object in; a frozen,
|
|
20
|
+
* normalized project out. A malformed envelope is `JS0001`; a duplicate
|
|
21
|
+
* file name is `JS0002`.
|
|
22
|
+
* @param {string | object} input
|
|
23
|
+
* @returns {any}
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseProject(input: string | object): any;
|
|
26
|
+
/**
|
|
27
|
+
* The file with this name, or `null`.
|
|
28
|
+
* @param {any} project
|
|
29
|
+
* @param {string} name
|
|
30
|
+
*/
|
|
31
|
+
export declare function fileOf(project: any, name: string): any;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Per-file validation — the honest heart of the per-file design.
|
|
3
|
+
* `validateFile(file)` dispatches on the file's kind and validates its
|
|
4
|
+
* `text` against THAT kind's grammar (never one composed mega-schema):
|
|
5
|
+
*
|
|
6
|
+
* - `app` → the composed jaren-app meta-schema (which `$ref`s the
|
|
7
|
+
* query + JSLT grammars) PLUS a headless render audit (a
|
|
8
|
+
* document that validates but throws on its first frame is
|
|
9
|
+
* still broken);
|
|
10
|
+
* - `jslt` / `query` → COMPILED by the engine WITH the operator registry,
|
|
11
|
+
* so host-registered operators ($npv, $sqrt) validate and a
|
|
12
|
+
* real error comes back as its own coded `JQ`/`JT` code with
|
|
13
|
+
* a docPath — the closed grammar would reject the operators;
|
|
14
|
+
* - `fsm` / `dag` / `model` → their published grammar (structural);
|
|
15
|
+
* - `schema` → compiled as a JSON Schema (is it well-formed?);
|
|
16
|
+
* - `state` / `data` → any JSON (structural only).
|
|
17
|
+
*
|
|
18
|
+
* Every result is `{ valid, kind, total, errors: [{ code, message,
|
|
19
|
+
* docPath }] }` — the shape the IDE's docked error strip reads.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Validate ONE file against its kind's grammar. Memoized on the file's
|
|
23
|
+
* identity; pass a fresh object to force a re-check.
|
|
24
|
+
* @param {{ name?: string, kind: string, text: string }} file
|
|
25
|
+
* @param {{ operators?: { toOptions: () => any } }} [options] - a host
|
|
26
|
+
* operator registry for the `jslt`/`query` kinds (defaults to the
|
|
27
|
+
* built-in math/finance/stats packs)
|
|
28
|
+
* @returns {{ valid: boolean, kind: string, total: number,
|
|
29
|
+
* errors: Array<{ code: string | null, message: string, docPath?: string }> }}
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateFile(file: {
|
|
32
|
+
name?: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
text: string;
|
|
35
|
+
}, options?: {
|
|
36
|
+
operators?: {
|
|
37
|
+
toOptions: () => any;
|
|
38
|
+
};
|
|
39
|
+
}): {
|
|
40
|
+
valid: boolean;
|
|
41
|
+
kind: string;
|
|
42
|
+
total: number;
|
|
43
|
+
errors: Array<{
|
|
44
|
+
code: string | null;
|
|
45
|
+
message: string;
|
|
46
|
+
docPath?: string;
|
|
47
|
+
}>;
|
|
48
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# PROJECT-FORMAT.md — the `jaren-project` document (v0.1)
|
|
2
|
+
|
|
3
|
+
A **project** is a jaren application seen as an IDE would see it: a small
|
|
4
|
+
tree of typed files, each one concern — a view, the actions, the state,
|
|
5
|
+
a schema, a data model, queries, a flow machine — the way HTML, CSS and
|
|
6
|
+
JavaScript are separate files. The document is a **thin envelope**; each
|
|
7
|
+
file's meaning lives in its own grammar, not in the envelope.
|
|
8
|
+
|
|
9
|
+
```jsonc
|
|
10
|
+
{
|
|
11
|
+
"project": "0.1",
|
|
12
|
+
"files": [
|
|
13
|
+
{ "name": "app.json", "kind": "app", "text": "…a jaren-app document…" },
|
|
14
|
+
{ "name": "series.query", "kind": "query", "text": "{ \"$npv\": [\"$.rate\", \"$.cf[*]\"] }" },
|
|
15
|
+
{ "name": "seed.data", "kind": "data", "text": "{ \"cf\": [-1000, 300, 400] }" }
|
|
16
|
+
],
|
|
17
|
+
"active": "app.json",
|
|
18
|
+
"layout": { "mode": "classic", "ratio": 0.5, "autorun": true }
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The envelope is published as a JSON Schema (`schemas/jaren-project.schema.json`
|
|
23
|
+
+ a draft-07 twin) and validated by `parseProject`, which returns a
|
|
24
|
+
frozen, normalized project (the layout defaulted, `active` resolved to a
|
|
25
|
+
real file).
|
|
26
|
+
|
|
27
|
+
## The file kinds
|
|
28
|
+
|
|
29
|
+
Each file declares a `kind`; its `text` is a JSON document validated
|
|
30
|
+
against **that kind's grammar at its own boundary** — never composed into
|
|
31
|
+
one meta-schema. `validateFile(file)` returns `{ valid, kind, total,
|
|
32
|
+
errors: [{ code, message, docPath }] }` — the shape an editor's error
|
|
33
|
+
strip reads.
|
|
34
|
+
|
|
35
|
+
| kind | the file is | validated by |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `app` | a `jaren-app` document | the composed app meta-schema (jslt + query by `$ref`) **plus a headless render audit** — a document that validates but throws on its first frame is still broken |
|
|
38
|
+
| `jslt` | a JSLT stylesheet | **compiled** by the engine with the operator registry |
|
|
39
|
+
| `query` | a query document | **compiled** by the engine with the operator registry |
|
|
40
|
+
| `state` / `data` | any JSON value (an input) | structural JSON only |
|
|
41
|
+
| `schema` | a JSON Schema | must be an object/boolean and compile |
|
|
42
|
+
| `fsm` / `dag` | a `jaren-fsm` / `jaren-dag` machine | its published flow grammar |
|
|
43
|
+
| `model` | a `jaren-model` store definition | the `jaren-model` grammar |
|
|
44
|
+
|
|
45
|
+
**Why per-file, not one composed schema.** The published `jaren-query` /
|
|
46
|
+
`jaren-jslt` grammars are *closed* — their operator vocabulary is
|
|
47
|
+
enumerated. A data query that uses a host-registered operator (`$npv`,
|
|
48
|
+
`$sqrt`) validates only when the studio's operator packs are mounted, so
|
|
49
|
+
`jslt`/`query` files are **compiled with the registry** rather than checked
|
|
50
|
+
against the closed grammar: registered operators pass, and a real mistake
|
|
51
|
+
comes back as its own coded code with a JSON Pointer (`JQ0002 — at /x:
|
|
52
|
+
unknown operator '$flter'`). Composing every file into one gate would make
|
|
53
|
+
that impossible; keeping each file on its own boundary is the honest
|
|
54
|
+
design, not a compromise. A host embeds its own vocabulary with
|
|
55
|
+
`validateFile(file, { operators })`.
|
|
56
|
+
|
|
57
|
+
## Assembly — files → runnable artifacts
|
|
58
|
+
|
|
59
|
+
`assembleArtifacts(project)` composes the files into the runnable set. v0.1
|
|
60
|
+
ships the **whole-document** contract: a runnable file (`app`, `fsm`,
|
|
61
|
+
`dag`, `model`, `jslt`, `query`, `schema`) is its own artifact
|
|
62
|
+
(`{ name, kind, role, doc, sourceFiles }`); `state`/`data` files are
|
|
63
|
+
inputs, not artifacts. **Fragment assembly** — composing separate `state`
|
|
64
|
+
+ `view` + `actions` files into ONE `jaren-app` document (the true
|
|
65
|
+
HTML/CSS/JS split) — is the model's headline enhancement and layers on
|
|
66
|
+
top without changing this contract (a future `sourceFiles` will list more
|
|
67
|
+
than one name).
|
|
68
|
+
|
|
69
|
+
## `layout` is frozen
|
|
70
|
+
|
|
71
|
+
`layout` is `{ mode: "classic" | "right" | "top", ratio: number,
|
|
72
|
+
autorun: boolean }`, defaulted to `{ classic, 0.5, true }`. It rides the
|
|
73
|
+
share link and the eject, so its shape is fixed at v0.1.
|
|
74
|
+
|
|
75
|
+
## `classifyChange` — reboot vs. hot-update
|
|
76
|
+
|
|
77
|
+
`classifyChange(prev, next)` reports, **per artifact**, whether a change
|
|
78
|
+
is `structural`, `state-only`, or `none`. It compares a structural key —
|
|
79
|
+
an `app` document *minus its `state`* — via the suite's own `contentKey`.
|
|
80
|
+
An IDE reads it to decide: a `state-only` edit hot-dispatches into a
|
|
81
|
+
running app (the user keeps scroll and inputs); a `structural` edit
|
|
82
|
+
reboots. Keeping this datum in the tested engine, and the policy in the
|
|
83
|
+
widget, is deliberate.
|
|
84
|
+
|
|
85
|
+
## Errors
|
|
86
|
+
|
|
87
|
+
Only the envelope raises a coded `StudioError`; a single file's grammar
|
|
88
|
+
problem is reported by `validateFile`, never thrown.
|
|
89
|
+
|
|
90
|
+
| code | meaning |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `JS0001` | the project document is invalid (bad JSON, or fails the envelope schema) |
|
|
93
|
+
| `JS0002` | a file name is duplicated in the project |
|
|
94
|
+
|
|
95
|
+
## Status
|
|
96
|
+
|
|
97
|
+
v0.1 is the headless **engine** — parse, per-file validate, assemble,
|
|
98
|
+
classify — plus the IDE **component** that mounts it: the file rail, the
|
|
99
|
+
debounced editor with its typing buffer, the run stage, the three layout
|
|
100
|
+
modes with a drag splitter, the phone pane switcher, save/load/share, and
|
|
101
|
+
an assistant that can list, read, write and run a project's files.
|
|
102
|
+
|
|
103
|
+
Still open, and tracked with their constraints in
|
|
104
|
+
[ROADMAP.md](../../../docs/ROADMAP.md): the `fsm`/`dag`/`model` kinds
|
|
105
|
+
validate but have no editor or runner, so they cannot be added from the
|
|
106
|
+
IDE; fragment assembly (one artifact from several files) is unbuilt;
|
|
107
|
+
the assistant authors files as free-form tool arguments rather than under
|
|
108
|
+
constrained decoding; there is no whole-project export (`.zip` eject);
|
|
109
|
+
`layout.autorun` is declared and never read; and the stage collects a
|
|
110
|
+
nested app's failures without showing them. Syntax highlighting stays a
|
|
111
|
+
non-goal — the editor is a plain `<textarea>` so the whole IDE remains a
|
|
112
|
+
JSLT document with no imperative chrome.
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jarenjs/studio",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.34.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./dist/types/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/types/index.d.ts",
|
|
12
|
+
"default": "./src/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./component": {
|
|
15
|
+
"types": "./dist/types/component/index.d.ts",
|
|
16
|
+
"default": "./src/component/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./schemas/*": "./schemas/*",
|
|
19
|
+
"./styles/studio.css": "./styles/studio.css",
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/types/",
|
|
24
|
+
"src/",
|
|
25
|
+
"docs/",
|
|
26
|
+
"schemas/",
|
|
27
|
+
"styles/"
|
|
28
|
+
],
|
|
29
|
+
"description": "The jaren project IDE: a multi-file project (schema, view, actions, model, queries, flow) is one document; each file is validated against its own grammar, assembled into runnable artifacts, and edited/hosted by the studio widget. The engine is headless; the component layer is the IDE.",
|
|
30
|
+
"author": "joham",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/jklarenbeek/jarenjs.git",
|
|
34
|
+
"directory": "components/studio"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=24"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public",
|
|
42
|
+
"registry": "https://registry.npmjs.org/"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"jaren",
|
|
46
|
+
"json",
|
|
47
|
+
"ide",
|
|
48
|
+
"studio",
|
|
49
|
+
"project",
|
|
50
|
+
"editor",
|
|
51
|
+
"codepen",
|
|
52
|
+
"playground",
|
|
53
|
+
"document",
|
|
54
|
+
"headless"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "npm run build:types",
|
|
58
|
+
"build:types": "tsc -p tsconfig.json",
|
|
59
|
+
"prepack": "npm run build:types"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@jarenjs/core": "^0.34.0",
|
|
63
|
+
"@jarenjs/validate": "^0.34.0",
|
|
64
|
+
"@jarenjs/json": "^0.34.0",
|
|
65
|
+
"@jarenjs/app": "^0.34.0",
|
|
66
|
+
"@jarenjs/flow": "^0.34.0",
|
|
67
|
+
"@jarenjs/db": "^0.34.0"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://jarenjs.dev/schemas/jaren-project/0.1/draft-07",
|
|
4
|
+
"title": "jaren-project (draft-07)",
|
|
5
|
+
"description": "The draft-07 twin of jaren-project, for tools pinned to draft-07. Mechanically equivalent to the 2020-12 canonical artifact.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["project", "files"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"project": { "enum": ["0.1"] },
|
|
11
|
+
"files": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"items": { "$ref": "#/definitions/file" }
|
|
14
|
+
},
|
|
15
|
+
"active": { "type": "string" },
|
|
16
|
+
"layout": { "$ref": "#/definitions/layout" }
|
|
17
|
+
},
|
|
18
|
+
"definitions": {
|
|
19
|
+
"file": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"required": ["name", "kind", "text"],
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"properties": {
|
|
24
|
+
"name": { "type": "string", "minLength": 1 },
|
|
25
|
+
"kind": {
|
|
26
|
+
"enum": ["app", "jslt", "query", "state", "data", "schema", "fsm", "dag", "model"]
|
|
27
|
+
},
|
|
28
|
+
"text": { "type": "string" }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"layout": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"properties": {
|
|
35
|
+
"mode": { "enum": ["classic", "right", "top"] },
|
|
36
|
+
"ratio": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
37
|
+
"autorun": { "type": "boolean" }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://jarenjs.dev/schemas/jaren-project/0.1",
|
|
4
|
+
"title": "jaren-project",
|
|
5
|
+
"description": "A jaren studio project: a thin envelope over a list of typed files. The envelope is schema-gated; each file's `text` is validated against ITS OWN kind grammar at its boundary, never composed into one mega-schema — so a data file may use host-registered operators the closed grammars forbid.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["project", "files"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"project": { "const": "0.1" },
|
|
11
|
+
"files": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"description": "The project's files, each a named unit of one concern.",
|
|
14
|
+
"items": { "$ref": "#/$defs/file" }
|
|
15
|
+
},
|
|
16
|
+
"active": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "The name of the file the editor shows (falls back to the first file)."
|
|
19
|
+
},
|
|
20
|
+
"layout": { "$ref": "#/$defs/layout" }
|
|
21
|
+
},
|
|
22
|
+
"$defs": {
|
|
23
|
+
"file": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"required": ["name", "kind", "text"],
|
|
26
|
+
"additionalProperties": false,
|
|
27
|
+
"properties": {
|
|
28
|
+
"name": { "type": "string", "minLength": 1 },
|
|
29
|
+
"kind": {
|
|
30
|
+
"description": "The file's grammar/role; its `text` is validated against this kind, not the envelope.",
|
|
31
|
+
"enum": ["app", "jslt", "query", "state", "data", "schema", "fsm", "dag", "model"]
|
|
32
|
+
},
|
|
33
|
+
"text": { "type": "string" }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"layout": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"description": "The IDE arrangement — frozen so it survives a share link and the eject.",
|
|
39
|
+
"additionalProperties": false,
|
|
40
|
+
"properties": {
|
|
41
|
+
"mode": { "enum": ["classic", "right", "top"] },
|
|
42
|
+
"ratio": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
43
|
+
"autorun": { "type": "boolean" }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|