unoverse 0.1.178 → 0.1.180
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.
|
@@ -307,6 +307,33 @@ resource "aws_instance" "app" {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
tags = { Name = "${var.name}-app" }
|
|
310
|
+
|
|
311
|
+
# THE IMAGE IS CHOSEN ONCE, AT BUILD, AND NEVER AGAIN BY ACCIDENT.
|
|
312
|
+
#
|
|
313
|
+
# `ami` is a force-replacement attribute: an instance's image cannot be swapped in place,
|
|
314
|
+
# so any change to it means DESTROY AND REBUILD, losing the 100 GB root volume and
|
|
315
|
+
# everything on it. The lookup above asks for `most_recent`, so the value feeding it
|
|
316
|
+
# changes whenever Canonical publishes, roughly monthly. Together those two facts arm a
|
|
317
|
+
# trap that fires on the first `unoverse deploy aws` after a publish, on a universe
|
|
318
|
+
# nobody has touched, with nothing in the repo to explain it.
|
|
319
|
+
#
|
|
320
|
+
# Observed 2026-08-26. BPP's box was built from ...-20260731; every deploy for four weeks
|
|
321
|
+
# got that same id back and planned nothing. Canonical published ...-20260826 at 07:52
|
|
322
|
+
# UTC, and the next deploy that morning planned to replace the server. Terraform was
|
|
323
|
+
# right and the config was wrong, and the only warning was one line in a plan that also
|
|
324
|
+
# said "Changing".
|
|
325
|
+
#
|
|
326
|
+
# An EXISTING universe now keeps the image it was built on; a NEW one still takes
|
|
327
|
+
# whatever is current when it is created, because the lookup is untouched and only drift
|
|
328
|
+
# is ignored. The trade is deliberate: nothing upgrades the OS for you. Patch in place
|
|
329
|
+
# with apt, and move to a newer image by rebuilding on purpose — a decision, rather than
|
|
330
|
+
# a side effect of deploying on the wrong day.
|
|
331
|
+
#
|
|
332
|
+
# DigitalOcean needs none of this: its droplet names a fixed slug (`ubuntu-22-04-x64`),
|
|
333
|
+
# a literal in the file that cannot drift.
|
|
334
|
+
lifecycle {
|
|
335
|
+
ignore_changes = [ami]
|
|
336
|
+
}
|
|
310
337
|
}
|
|
311
338
|
|
|
312
339
|
resource "aws_eip" "app" {
|
package/package.json
CHANGED
|
@@ -101,7 +101,12 @@ function lintFile(file) {
|
|
|
101
101
|
// manifest-only form must resolve a layout; an envelope-form template supplies its own.
|
|
102
102
|
const hasEnvelope = !!defPath(root, basename(root));
|
|
103
103
|
if (!hasEnvelope) {
|
|
104
|
-
|
|
104
|
+
// The base defaults to the FIRST DECLARED STATE (states own layouts and
|
|
105
|
+
// declaration order ranks them); "main" only when no states block exists.
|
|
106
|
+
const firstState = json.states && typeof json.states === "object" && !Array.isArray(json.states)
|
|
107
|
+
? Object.keys(json.states)[0]
|
|
108
|
+
: undefined;
|
|
109
|
+
const layoutName = json.layout ?? firstState ?? "main";
|
|
105
110
|
if (!defPath(join(root, "layouts"), layoutName))
|
|
106
111
|
report("error", file, `manifest.layout "${layoutName}" → layouts/${layoutName} does not exist (and no <name> envelope) (docs.unoverse.ai/design/apps)`);
|
|
107
112
|
}
|
|
@@ -123,7 +128,7 @@ function lintFile(file) {
|
|
|
123
128
|
report("error", file, `manifest "states" must be an object — the template tree: { <base>: { states: {…} }, <reaction>: {}, … } (STATE_MODEL §5)`);
|
|
124
129
|
} else {
|
|
125
130
|
const names = Object.keys(json.states);
|
|
126
|
-
const base = json.layout ?? "main";
|
|
131
|
+
const base = json.layout ?? Object.keys(json.states)[0] ?? "main";
|
|
127
132
|
if (!names.includes(base))
|
|
128
133
|
report("error", file, `the template tree must include the BASE state "${base}" (named for the default layout) — the ladder derives as the top level minus it (STATE_MODEL §5)`);
|
|
129
134
|
const subs = [];
|
|
@@ -279,16 +284,21 @@ function lintFile(file) {
|
|
|
279
284
|
|
|
280
285
|
if (isEnvelope) {
|
|
281
286
|
// COMPONENT envelope (templates have no envelope — their manifest is it).
|
|
282
|
-
|
|
287
|
+
// The declaration word is `type:`; `kind:` is the legacy spelling, still read.
|
|
288
|
+
const declared = json.type ?? json.kind;
|
|
289
|
+
for (const req of ["name", "root"])
|
|
283
290
|
if (json[req] === undefined) report("error", file, `envelope missing "${req}" (docs.unoverse.ai/design/sdui-and-mcp-apps)`);
|
|
284
|
-
if (
|
|
285
|
-
|
|
286
|
-
|
|
291
|
+
if (declared === undefined) report("error", file, `envelope missing "type" (docs.unoverse.ai/design/sdui-and-mcp-apps)`);
|
|
292
|
+
if (json.kind !== undefined && json.type === undefined)
|
|
293
|
+
report("warn", file, `"kind" is the legacy spelling of the declaration — write "type: ${json.kind}"`);
|
|
294
|
+
if (declared && !["component", "template", "atom"].includes(declared))
|
|
295
|
+
report("error", file, `unknown type "${declared}"`);
|
|
296
|
+
if (declared === "component" && !json.category)
|
|
287
297
|
report("warn", file, `component has no "category". Used to group it in the palette (docs.unoverse.ai/design/sdui-and-mcp-apps)`);
|
|
288
298
|
if (json.root) walkNode(json.root, file, root);
|
|
289
299
|
|
|
290
300
|
// ── the contained-microapp discipline (mirrors microapp-structure.test.ts) ──
|
|
291
|
-
if (
|
|
301
|
+
if (declared === "component") {
|
|
292
302
|
const hasLayouts = existsSync(join(root, "layouts"));
|
|
293
303
|
const statesDir = join(root, "states");
|
|
294
304
|
const stateFiles = existsSync(statesDir)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// ── ground truth (mirrors design/_schema/unoverse.schema.json + server guards) ──
|
|
14
14
|
export const PRIMITIVES = new Set([
|
|
15
15
|
"Box", "Stack", "Row", "Column", "Each", "Switch", "ComponentSlot", "Timeline",
|
|
16
|
-
"Text", "Image", "Button", "Input", "Select", "Markdown", "Skeleton", "Icon", "Ref", "Orb",
|
|
16
|
+
"Text", "Image", "Button", "Input", "Select", "Markdown", "Skeleton", "Icon", "Ref", "Orb", "Template",
|
|
17
17
|
]);
|
|
18
18
|
export const CONDITION_KEYS = new Set(["field", "eq", "ne", "in"]);
|
|
19
19
|
|
|
@@ -56,6 +56,10 @@ export const PRIMITIVE_PROPS = {
|
|
|
56
56
|
Switch: ["on", "cases", "fallback"],
|
|
57
57
|
Ref: ["ref", "props", "with", "children", "name"],
|
|
58
58
|
ComponentSlot: ["select", "frame", "fallback", "pinned", "appWidth"],
|
|
59
|
+
// THE MOUNT (UNOVERSE_TEMPLATE_MODEL §12): a layout place that runs a mounted
|
|
60
|
+
// template's machinery. `name` addresses the manifest's `templates:` entry;
|
|
61
|
+
// `frame` is the dressing drawn around it when occupied.
|
|
62
|
+
Template: ["name", "frame", "template", "director", "appWidth"],
|
|
59
63
|
Timeline: ["user", "assistant", "userData", "assistantData", "autoScroll"],
|
|
60
64
|
};
|
|
61
65
|
|