@thatopen/services 0.6.0 → 0.8.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 +62 -62
- package/dist/built-in/index.d.ts +485 -100
- package/dist/cli/lib/revit-addin.d.ts +8 -0
- package/dist/cli.js +135 -24
- package/dist/index.cjs.js +2 -2
- package/dist/index.es.js +41 -50
- package/dist/vite-env.d.ts +1 -1
- package/docs/ai-quickstart.md +138 -4
- package/docs/cli/create.md +29 -29
- package/docs/cli/local-server.md +38 -38
- package/docs/cli/login.md +32 -32
- package/docs/cli/publish.md +31 -31
- package/docs/cli/run.md +26 -26
- package/docs/cli/serve.md +43 -43
- package/docs/cli/swap.md +30 -30
- package/docs/flow-architecture.md +1 -1
- package/docs/flow-plugin-guide.md +27 -1
- package/docs/platform-token.md +81 -0
- package/docs/revit-collab-quickstart.md +115 -35
- package/docs/rhino-interop-quickstart.md +458 -0
- package/package.json +1 -1
- package/resources/AGENTS.md +1 -1
- package/src/built-in/CDEManager/example.ts +214 -214
- package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
- package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
- package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
- package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
- package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
- package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
- package/src/built-in/UIManager/src/app/example.ts +97 -97
- package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
- package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
- package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
- package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
- package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
- package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
- package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
- package/src/built-in/UIManager/src/app/examples/ProjectFileStorage/example.ts +150 -150
- package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
- package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -183
- package/src/built-in/UIManager/src/git-history-panel/example.ts +70 -70
- package/src/built-in/UIManager/src/models-list/example.ts +122 -122
- package/src/built-in/UIManager/src/topics-panel/example.ts +170 -170
- package/src/built-in/index.ts +485 -100
- package/src/cli/templates/app/index.html +35 -35
- package/src/cli/templates/app/package.json +32 -32
- package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
- package/src/cli/templates/app/src/bim-components/index.ts +1 -1
- package/src/cli/templates/app/src/globals.ts +1 -1
- package/src/cli/templates/app/src/main.history.ts +191 -0
- package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
- package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
- package/src/cli/templates/app/src/ui-components/index.ts +2 -2
- package/src/cli/templates/app/tsconfig.json +16 -16
- package/src/cli/templates/app/vite.config.js +57 -57
- package/src/cli/templates/cloud-component/README.md +205 -205
- package/src/cli/templates/cloud-component/_thatopen +5 -5
- package/src/cli/templates/cloud-component/declarations.json +4 -4
- package/src/cli/templates/cloud-component/package.json +21 -21
- package/src/cli/templates/cloud-component/src/main.ts +70 -70
- package/src/cli/templates/cloud-component/tsconfig.json +16 -16
- package/src/cli/templates/cloud-component/vite.config.js +46 -46
- package/src/cli/templates/shared/AGENTS.md +8 -8
- package/src/cli/templates/shared/CLAUDE.md +1 -1
- package/src/cli/templates/shared/_gitignore +5 -5
|
@@ -1,230 +1,230 @@
|
|
|
1
|
-
/* MD
|
|
2
|
-
## Metadata schema — define, stage, and publish field definitions
|
|
3
|
-
---
|
|
4
|
-
Every project has its own vocabulary for describing files: discipline codes,
|
|
5
|
-
revision phases, area zones, review statuses. A metadata schema defines which
|
|
6
|
-
fields exist, what values they accept, and whether they are mandatory. Without a
|
|
7
|
-
structured schema, each user invents their own labels and the file list becomes
|
|
8
|
-
impossible to filter or report on consistently.
|
|
9
|
-
|
|
10
|
-
CDEManager stages schema changes in memory before anything is written. New fields
|
|
11
|
-
start with a placeholder name and get their permanent key from their label at save
|
|
12
|
-
time. Edits to existing fields are tracked per-key so the UI can show a diff, and
|
|
13
|
-
changes can be reverted field by field. Deletions are staged as null markers, which
|
|
14
|
-
lets the UI still show the field as "pending removal" rather than making it disappear
|
|
15
|
-
immediately.
|
|
16
|
-
|
|
17
|
-
This tutorial covers entering schema edit mode; adding new fields and updating their
|
|
18
|
-
definitions; observing that updating a field back to its original values removes it
|
|
19
|
-
from the pending list; staging a deletion and restoring it; and committing the full
|
|
20
|
-
batch, including how new field keys are derived from their label text.
|
|
21
|
-
|
|
22
|
-
By the end, you'll understand the full schema editing lifecycle and how the machine
|
|
23
|
-
state carries a complete diff — additions, updates, and deletions — before any
|
|
24
|
-
changes reach the backend.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
import * as OBC from "@thatopen/components";
|
|
28
|
-
// In production platform apps, CDEManager is loaded through the platform client:
|
|
29
|
-
// const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
|
|
30
|
-
// Here we import directly from source for local testing.
|
|
31
|
-
import { CDEManager } from "../../index";
|
|
32
|
-
import type { CDEMetadataField } from "../../src/types";
|
|
33
|
-
|
|
34
|
-
/* MD
|
|
35
|
-
### 🛠️ Setting up
|
|
36
|
-
Schema editing is gated by the manage permission. We use the mock context pattern
|
|
37
|
-
to load permissions, then clear the context — schema staging is entirely in-memory
|
|
38
|
-
and does not need a backend connection. Saving without a context skips persistence
|
|
39
|
-
but still applies the changes locally, which is enough to verify the machine behavior.
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
const components = new OBC.Components();
|
|
43
|
-
const cde = components.get(CDEManager);
|
|
44
|
-
|
|
45
|
-
const output = document.getElementById("output") as HTMLPreElement;
|
|
46
|
-
const log = (msg: string) => { console.log(msg); output.textContent += msg + "\n"; };
|
|
47
|
-
|
|
48
|
-
cde.ctx = {
|
|
49
|
-
client: {
|
|
50
|
-
checkPermissionBatch: async () => Array(5).fill({ hasPermission: true }),
|
|
51
|
-
getProjectData: async () => ({ project: { title: "" }, users: [] }),
|
|
52
|
-
listFolders: async () => [],
|
|
53
|
-
} as any,
|
|
54
|
-
projectId: "mock",
|
|
55
|
-
};
|
|
56
|
-
await cde.loadFolders();
|
|
57
|
-
cde.ctx = undefined;
|
|
58
|
-
|
|
59
|
-
// Seed an initial schema to work with.
|
|
60
|
-
cde.metadataSchema = {
|
|
61
|
-
discipline: { label: "Discipline", type: "string", required: true },
|
|
62
|
-
phase: { label: "Phase", type: "string", required: false },
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
cde.onMachineStateChanged.add((s) => log(` → ${s.kind}`));
|
|
66
|
-
|
|
67
|
-
/* MD
|
|
68
|
-
### ➕ Adding and updating fields
|
|
69
|
-
New fields are added with a placeholder definition and a machine-generated temporary
|
|
70
|
-
key. The actual key is derived from the field label when the schema is saved, so the
|
|
71
|
-
caller only needs to set a meaningful label — not invent a key manually.
|
|
72
|
-
|
|
73
|
-
Any update to a field is stored as a replacement of the entire field definition under
|
|
74
|
-
that key. If all properties are changed back to the original values, the edit is
|
|
75
|
-
removed from the pending list automatically — no explicit undo step is needed.
|
|
76
|
-
*/
|
|
77
|
-
|
|
78
|
-
log("\n--- add and update ---");
|
|
79
|
-
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
80
|
-
|
|
81
|
-
// New field starts as "New Field" with a temp key like "field_1234567890".
|
|
82
|
-
cde.sendMachineEvent({ type: "ADD_METADATA_FIELD" });
|
|
83
|
-
|
|
84
|
-
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
85
|
-
const tempKey = Object.keys(cde.machineState.pendingEdits).find(k => k.startsWith("field_"))!;
|
|
86
|
-
log(` new field temp key: ${tempKey.slice(0, 10)}…`);
|
|
87
|
-
|
|
88
|
-
// Give it a real label and type.
|
|
89
|
-
cde.sendMachineEvent({
|
|
90
|
-
type: "UPDATE_METADATA_FIELD",
|
|
91
|
-
key: tempKey,
|
|
92
|
-
value: { label: "Issue Phase", type: "list", required: false, options: [{ value: "open", label: "Open" }, { value: "closed", label: "Closed" }] },
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const pending = Object.keys(cde.machineState.pendingEdits);
|
|
96
|
-
log(` pending fields after update: ${pending.length}`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Update an existing field — changes are tracked separately from new ones.
|
|
100
|
-
cde.sendMachineEvent({
|
|
101
|
-
type: "UPDATE_METADATA_FIELD",
|
|
102
|
-
key: "discipline",
|
|
103
|
-
value: { label: "Discipline", type: "string", required: false },
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
107
|
-
log(` discipline pending: ${Object.keys(cde.machineState.pendingEdits).includes("discipline")}`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
cde.sendMachineEvent({ type: "CANCEL_CONFIGURE_METADATA_SCHEMA" });
|
|
111
|
-
|
|
112
|
-
/* MD
|
|
113
|
-
### 🔍 Smart update diff
|
|
114
|
-
Updating a field back to exactly its original values removes it from the pending
|
|
115
|
-
list. This keeps the diff clean — the UI doesn't need to track which changes were
|
|
116
|
-
reverted, it just reads the pending list.
|
|
117
|
-
*/
|
|
118
|
-
|
|
119
|
-
log("\n--- smart diff on update ---");
|
|
120
|
-
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
121
|
-
|
|
122
|
-
// Change discipline's required flag.
|
|
123
|
-
cde.sendMachineEvent({
|
|
124
|
-
type: "UPDATE_METADATA_FIELD",
|
|
125
|
-
key: "discipline",
|
|
126
|
-
value: { label: "Discipline", type: "string", required: false },
|
|
127
|
-
});
|
|
128
|
-
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
129
|
-
log(` discipline in pending: ${Object.keys(cde.machineState.pendingEdits).includes("discipline")}`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Revert to the original — removes it from the pending list automatically.
|
|
133
|
-
cde.sendMachineEvent({
|
|
134
|
-
type: "UPDATE_METADATA_FIELD",
|
|
135
|
-
key: "discipline",
|
|
136
|
-
value: { label: "Discipline", type: "string", required: true },
|
|
137
|
-
});
|
|
138
|
-
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
139
|
-
log(` discipline in pending after revert: ${Object.keys(cde.machineState.pendingEdits).includes("discipline")}`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
cde.sendMachineEvent({ type: "CANCEL_CONFIGURE_METADATA_SCHEMA" });
|
|
143
|
-
|
|
144
|
-
/* MD
|
|
145
|
-
### 🗑️ Deleting and restoring fields
|
|
146
|
-
Deleting an existing field marks it as null in the pending list rather than removing
|
|
147
|
-
it outright, so the UI can still show it as "pending deletion" and let the user
|
|
148
|
-
change their mind. Restoring removes the null marker — the field is back to its
|
|
149
|
-
current state as if it was never staged for deletion.
|
|
150
|
-
|
|
151
|
-
Deleting a field that was just added (and therefore does not yet exist in the schema)
|
|
152
|
-
removes it from the pending list entirely — there is nothing to mark for deletion
|
|
153
|
-
because there is nothing to undo.
|
|
154
|
-
*/
|
|
155
|
-
|
|
156
|
-
log("\n--- delete and restore ---");
|
|
157
|
-
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
158
|
-
|
|
159
|
-
// Stage deletion of an existing field.
|
|
160
|
-
cde.sendMachineEvent({ type: "DELETE_METADATA_FIELD", key: "phase" });
|
|
161
|
-
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
162
|
-
log(` phase value in pending: ${JSON.stringify(cde.machineState.pendingEdits["phase"])}`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Restore it — null marker removed.
|
|
166
|
-
cde.sendMachineEvent({ type: "RESTORE_METADATA_FIELD", key: "phase" });
|
|
167
|
-
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
168
|
-
log(` phase in pending after restore: ${"phase" in cde.machineState.pendingEdits}`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Add a new field, then delete it — removes it entirely (no null marker needed).
|
|
172
|
-
cde.sendMachineEvent({ type: "ADD_METADATA_FIELD" });
|
|
173
|
-
const newTempKey = Object.keys(
|
|
174
|
-
(cde.machineState as any).pendingEdits
|
|
175
|
-
).find((k: string) => k.startsWith("field_"))!;
|
|
176
|
-
cde.sendMachineEvent({ type: "DELETE_METADATA_FIELD", key: newTempKey });
|
|
177
|
-
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
178
|
-
log(` pending after deleting new field: ${Object.keys(cde.machineState.pendingEdits).length}`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
cde.sendMachineEvent({ type: "CANCEL_CONFIGURE_METADATA_SCHEMA" });
|
|
182
|
-
|
|
183
|
-
/* MD
|
|
184
|
-
### 💾 Committing the schema
|
|
185
|
-
When the schema is saved, new fields get their permanent key derived from their label
|
|
186
|
-
— spaces become underscores, special characters are stripped. If the derived key
|
|
187
|
-
would collide with an existing one, a numeric suffix is appended automatically.
|
|
188
|
-
After the commit, the pending list is empty and the schema reflects all the staged
|
|
189
|
-
changes.
|
|
190
|
-
*/
|
|
191
|
-
|
|
192
|
-
log("\n--- commit ---");
|
|
193
|
-
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
194
|
-
|
|
195
|
-
// Add a new field: label "Issue Phase" → key will become "issue_phase".
|
|
196
|
-
cde.sendMachineEvent({ type: "ADD_METADATA_FIELD" });
|
|
197
|
-
const commitTempKey = Object.keys(
|
|
198
|
-
(cde.machineState as any).pendingEdits
|
|
199
|
-
).find((k: string) => k.startsWith("field_"))!;
|
|
200
|
-
cde.sendMachineEvent({
|
|
201
|
-
type: "UPDATE_METADATA_FIELD",
|
|
202
|
-
key: commitTempKey,
|
|
203
|
-
value: { label: "Issue Phase", type: "string", required: false },
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// Update an existing field.
|
|
207
|
-
cde.sendMachineEvent({
|
|
208
|
-
type: "UPDATE_METADATA_FIELD",
|
|
209
|
-
key: "discipline",
|
|
210
|
-
value: { label: "Discipline", type: "string", required: false },
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Stage deletion of "phase".
|
|
214
|
-
cde.sendMachineEvent({ type: "DELETE_METADATA_FIELD", key: "phase" });
|
|
215
|
-
|
|
216
|
-
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
217
|
-
log(` pending before save: ${Object.keys(cde.machineState.pendingEdits).length} change(s)`);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Save applies changes locally even without a backend connection.
|
|
221
|
-
await new Promise<void>(resolve => {
|
|
222
|
-
cde.onMachineStateChanged.add((s) => { if (s.kind === "idle") resolve(); });
|
|
223
|
-
cde.sendMachineEvent({ type: "SAVE_METADATA_SCHEMA" });
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
log(` state after save: ${cde.machineState.kind}`);
|
|
227
|
-
log(` schema keys: [${Object.keys(cde.metadataSchema).join(", ")}]`);
|
|
228
|
-
log(` discipline.required: ${cde.metadataSchema["discipline"]?.required}`);
|
|
229
|
-
log(` issue_phase exists: ${"issue_phase" in cde.metadataSchema}`);
|
|
230
|
-
log(` phase removed: ${"phase" in cde.metadataSchema ? "no" : "yes"}`);
|
|
1
|
+
/* MD
|
|
2
|
+
## Metadata schema — define, stage, and publish field definitions
|
|
3
|
+
---
|
|
4
|
+
Every project has its own vocabulary for describing files: discipline codes,
|
|
5
|
+
revision phases, area zones, review statuses. A metadata schema defines which
|
|
6
|
+
fields exist, what values they accept, and whether they are mandatory. Without a
|
|
7
|
+
structured schema, each user invents their own labels and the file list becomes
|
|
8
|
+
impossible to filter or report on consistently.
|
|
9
|
+
|
|
10
|
+
CDEManager stages schema changes in memory before anything is written. New fields
|
|
11
|
+
start with a placeholder name and get their permanent key from their label at save
|
|
12
|
+
time. Edits to existing fields are tracked per-key so the UI can show a diff, and
|
|
13
|
+
changes can be reverted field by field. Deletions are staged as null markers, which
|
|
14
|
+
lets the UI still show the field as "pending removal" rather than making it disappear
|
|
15
|
+
immediately.
|
|
16
|
+
|
|
17
|
+
This tutorial covers entering schema edit mode; adding new fields and updating their
|
|
18
|
+
definitions; observing that updating a field back to its original values removes it
|
|
19
|
+
from the pending list; staging a deletion and restoring it; and committing the full
|
|
20
|
+
batch, including how new field keys are derived from their label text.
|
|
21
|
+
|
|
22
|
+
By the end, you'll understand the full schema editing lifecycle and how the machine
|
|
23
|
+
state carries a complete diff — additions, updates, and deletions — before any
|
|
24
|
+
changes reach the backend.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import * as OBC from "@thatopen/components";
|
|
28
|
+
// In production platform apps, CDEManager is loaded through the platform client:
|
|
29
|
+
// const cde = await client.initBuiltInComponent(CDEManager.uuid, components);
|
|
30
|
+
// Here we import directly from source for local testing.
|
|
31
|
+
import { CDEManager } from "../../index";
|
|
32
|
+
import type { CDEMetadataField } from "../../src/types";
|
|
33
|
+
|
|
34
|
+
/* MD
|
|
35
|
+
### 🛠️ Setting up
|
|
36
|
+
Schema editing is gated by the manage permission. We use the mock context pattern
|
|
37
|
+
to load permissions, then clear the context — schema staging is entirely in-memory
|
|
38
|
+
and does not need a backend connection. Saving without a context skips persistence
|
|
39
|
+
but still applies the changes locally, which is enough to verify the machine behavior.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const components = new OBC.Components();
|
|
43
|
+
const cde = components.get(CDEManager);
|
|
44
|
+
|
|
45
|
+
const output = document.getElementById("output") as HTMLPreElement;
|
|
46
|
+
const log = (msg: string) => { console.log(msg); output.textContent += msg + "\n"; };
|
|
47
|
+
|
|
48
|
+
cde.ctx = {
|
|
49
|
+
client: {
|
|
50
|
+
checkPermissionBatch: async () => Array(5).fill({ hasPermission: true }),
|
|
51
|
+
getProjectData: async () => ({ project: { title: "" }, users: [] }),
|
|
52
|
+
listFolders: async () => [],
|
|
53
|
+
} as any,
|
|
54
|
+
projectId: "mock",
|
|
55
|
+
};
|
|
56
|
+
await cde.loadFolders();
|
|
57
|
+
cde.ctx = undefined;
|
|
58
|
+
|
|
59
|
+
// Seed an initial schema to work with.
|
|
60
|
+
cde.metadataSchema = {
|
|
61
|
+
discipline: { label: "Discipline", type: "string", required: true },
|
|
62
|
+
phase: { label: "Phase", type: "string", required: false },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
cde.onMachineStateChanged.add((s) => log(` → ${s.kind}`));
|
|
66
|
+
|
|
67
|
+
/* MD
|
|
68
|
+
### ➕ Adding and updating fields
|
|
69
|
+
New fields are added with a placeholder definition and a machine-generated temporary
|
|
70
|
+
key. The actual key is derived from the field label when the schema is saved, so the
|
|
71
|
+
caller only needs to set a meaningful label — not invent a key manually.
|
|
72
|
+
|
|
73
|
+
Any update to a field is stored as a replacement of the entire field definition under
|
|
74
|
+
that key. If all properties are changed back to the original values, the edit is
|
|
75
|
+
removed from the pending list automatically — no explicit undo step is needed.
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
log("\n--- add and update ---");
|
|
79
|
+
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
80
|
+
|
|
81
|
+
// New field starts as "New Field" with a temp key like "field_1234567890".
|
|
82
|
+
cde.sendMachineEvent({ type: "ADD_METADATA_FIELD" });
|
|
83
|
+
|
|
84
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
85
|
+
const tempKey = Object.keys(cde.machineState.pendingEdits).find(k => k.startsWith("field_"))!;
|
|
86
|
+
log(` new field temp key: ${tempKey.slice(0, 10)}…`);
|
|
87
|
+
|
|
88
|
+
// Give it a real label and type.
|
|
89
|
+
cde.sendMachineEvent({
|
|
90
|
+
type: "UPDATE_METADATA_FIELD",
|
|
91
|
+
key: tempKey,
|
|
92
|
+
value: { label: "Issue Phase", type: "list", required: false, options: [{ value: "open", label: "Open" }, { value: "closed", label: "Closed" }] },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const pending = Object.keys(cde.machineState.pendingEdits);
|
|
96
|
+
log(` pending fields after update: ${pending.length}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Update an existing field — changes are tracked separately from new ones.
|
|
100
|
+
cde.sendMachineEvent({
|
|
101
|
+
type: "UPDATE_METADATA_FIELD",
|
|
102
|
+
key: "discipline",
|
|
103
|
+
value: { label: "Discipline", type: "string", required: false },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
107
|
+
log(` discipline pending: ${Object.keys(cde.machineState.pendingEdits).includes("discipline")}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
cde.sendMachineEvent({ type: "CANCEL_CONFIGURE_METADATA_SCHEMA" });
|
|
111
|
+
|
|
112
|
+
/* MD
|
|
113
|
+
### 🔍 Smart update diff
|
|
114
|
+
Updating a field back to exactly its original values removes it from the pending
|
|
115
|
+
list. This keeps the diff clean — the UI doesn't need to track which changes were
|
|
116
|
+
reverted, it just reads the pending list.
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
log("\n--- smart diff on update ---");
|
|
120
|
+
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
121
|
+
|
|
122
|
+
// Change discipline's required flag.
|
|
123
|
+
cde.sendMachineEvent({
|
|
124
|
+
type: "UPDATE_METADATA_FIELD",
|
|
125
|
+
key: "discipline",
|
|
126
|
+
value: { label: "Discipline", type: "string", required: false },
|
|
127
|
+
});
|
|
128
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
129
|
+
log(` discipline in pending: ${Object.keys(cde.machineState.pendingEdits).includes("discipline")}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Revert to the original — removes it from the pending list automatically.
|
|
133
|
+
cde.sendMachineEvent({
|
|
134
|
+
type: "UPDATE_METADATA_FIELD",
|
|
135
|
+
key: "discipline",
|
|
136
|
+
value: { label: "Discipline", type: "string", required: true },
|
|
137
|
+
});
|
|
138
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
139
|
+
log(` discipline in pending after revert: ${Object.keys(cde.machineState.pendingEdits).includes("discipline")}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
cde.sendMachineEvent({ type: "CANCEL_CONFIGURE_METADATA_SCHEMA" });
|
|
143
|
+
|
|
144
|
+
/* MD
|
|
145
|
+
### 🗑️ Deleting and restoring fields
|
|
146
|
+
Deleting an existing field marks it as null in the pending list rather than removing
|
|
147
|
+
it outright, so the UI can still show it as "pending deletion" and let the user
|
|
148
|
+
change their mind. Restoring removes the null marker — the field is back to its
|
|
149
|
+
current state as if it was never staged for deletion.
|
|
150
|
+
|
|
151
|
+
Deleting a field that was just added (and therefore does not yet exist in the schema)
|
|
152
|
+
removes it from the pending list entirely — there is nothing to mark for deletion
|
|
153
|
+
because there is nothing to undo.
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
log("\n--- delete and restore ---");
|
|
157
|
+
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
158
|
+
|
|
159
|
+
// Stage deletion of an existing field.
|
|
160
|
+
cde.sendMachineEvent({ type: "DELETE_METADATA_FIELD", key: "phase" });
|
|
161
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
162
|
+
log(` phase value in pending: ${JSON.stringify(cde.machineState.pendingEdits["phase"])}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Restore it — null marker removed.
|
|
166
|
+
cde.sendMachineEvent({ type: "RESTORE_METADATA_FIELD", key: "phase" });
|
|
167
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
168
|
+
log(` phase in pending after restore: ${"phase" in cde.machineState.pendingEdits}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Add a new field, then delete it — removes it entirely (no null marker needed).
|
|
172
|
+
cde.sendMachineEvent({ type: "ADD_METADATA_FIELD" });
|
|
173
|
+
const newTempKey = Object.keys(
|
|
174
|
+
(cde.machineState as any).pendingEdits
|
|
175
|
+
).find((k: string) => k.startsWith("field_"))!;
|
|
176
|
+
cde.sendMachineEvent({ type: "DELETE_METADATA_FIELD", key: newTempKey });
|
|
177
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
178
|
+
log(` pending after deleting new field: ${Object.keys(cde.machineState.pendingEdits).length}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
cde.sendMachineEvent({ type: "CANCEL_CONFIGURE_METADATA_SCHEMA" });
|
|
182
|
+
|
|
183
|
+
/* MD
|
|
184
|
+
### 💾 Committing the schema
|
|
185
|
+
When the schema is saved, new fields get their permanent key derived from their label
|
|
186
|
+
— spaces become underscores, special characters are stripped. If the derived key
|
|
187
|
+
would collide with an existing one, a numeric suffix is appended automatically.
|
|
188
|
+
After the commit, the pending list is empty and the schema reflects all the staged
|
|
189
|
+
changes.
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
log("\n--- commit ---");
|
|
193
|
+
cde.sendMachineEvent({ type: "START_CONFIGURE_METADATA_SCHEMA" });
|
|
194
|
+
|
|
195
|
+
// Add a new field: label "Issue Phase" → key will become "issue_phase".
|
|
196
|
+
cde.sendMachineEvent({ type: "ADD_METADATA_FIELD" });
|
|
197
|
+
const commitTempKey = Object.keys(
|
|
198
|
+
(cde.machineState as any).pendingEdits
|
|
199
|
+
).find((k: string) => k.startsWith("field_"))!;
|
|
200
|
+
cde.sendMachineEvent({
|
|
201
|
+
type: "UPDATE_METADATA_FIELD",
|
|
202
|
+
key: commitTempKey,
|
|
203
|
+
value: { label: "Issue Phase", type: "string", required: false },
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Update an existing field.
|
|
207
|
+
cde.sendMachineEvent({
|
|
208
|
+
type: "UPDATE_METADATA_FIELD",
|
|
209
|
+
key: "discipline",
|
|
210
|
+
value: { label: "Discipline", type: "string", required: false },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Stage deletion of "phase".
|
|
214
|
+
cde.sendMachineEvent({ type: "DELETE_METADATA_FIELD", key: "phase" });
|
|
215
|
+
|
|
216
|
+
if (cde.machineState.kind === "configuringMetadataSchema") {
|
|
217
|
+
log(` pending before save: ${Object.keys(cde.machineState.pendingEdits).length} change(s)`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Save applies changes locally even without a backend connection.
|
|
221
|
+
await new Promise<void>(resolve => {
|
|
222
|
+
cde.onMachineStateChanged.add((s) => { if (s.kind === "idle") resolve(); });
|
|
223
|
+
cde.sendMachineEvent({ type: "SAVE_METADATA_SCHEMA" });
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
log(` state after save: ${cde.machineState.kind}`);
|
|
227
|
+
log(` schema keys: [${Object.keys(cde.metadataSchema).join(", ")}]`);
|
|
228
|
+
log(` discipline.required: ${cde.metadataSchema["discipline"]?.required}`);
|
|
229
|
+
log(` issue_phase exists: ${"issue_phase" in cde.metadataSchema}`);
|
|
230
|
+
log(` phase removed: ${"phase" in cde.metadataSchema ? "no" : "yes"}`);
|