@manudota/artist-mcp 2.1.0 → 2.1.1-staging.98

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.
@@ -76,6 +76,26 @@ may have edited since.
76
76
  - Where a decision genuinely lands in two places, that is two fragments, each
77
77
  named for where it goes. Not one merged block covering both.
78
78
 
79
+ ### A table is the exception, and only because OneNote makes it one
80
+
81
+ OneNote supports no change to a single row or cell. The tool can replace a whole
82
+ table and nothing smaller, so where the decision lands in a table the smallest
83
+ fragment it can *apply* is the entire table.
84
+
85
+ That does not loosen anything above; it moves the care somewhere else. **Every
86
+ cell that is not changing has to be carried across exactly as it reads now** —
87
+ copy them from the rows the preview lists rather than retyping them from memory,
88
+ because a cell left out of the markup is not a cell left alone, it is a value
89
+ destroyed. `UNKNOWN` stays `UNKNOWN` in those words, and a column the musician
90
+ never fills stays empty.
91
+
92
+ Say what the change is, not what the mechanism is. "Changing Honorar to 1450" is
93
+ the decision; that the whole table is rewritten to do it is worth one sentence,
94
+ because they are approving the loss of everything in it if the markup is wrong.
95
+
96
+ A patch to be pasted is unaffected: they place a row themselves, so it stays one
97
+ row.
98
+
79
99
  A whole page is a fragment only when the whole page is what they settled — the
80
100
  consolidated page in `policy:divergence`, once every conflicting field has been
81
101
  decided, which goes somewhere new rather than over either original. Wanting a
@@ -175,6 +195,49 @@ Owner is the only cell they settled. Due is `UNKNOWN` because no date was given
175
195
  is `UNKNOWN` because "not started" is a reasonable guess and still a guess. The
176
196
  page says nothing about any of this until they paste it.
177
197
 
198
+ ## Table markup
199
+
200
+ A table cannot be edited a cell at a time — OneNote supports no update to a row
201
+ or a cell, so changing one value rewrites the whole table. Every cell that is
202
+ not changing has to be carried across exactly as it reads.
203
+
204
+ Formatting the original carried and the replacement leaves out is now carried
205
+ across for you, and the preview says which parts it is keeping. Do not rely on
206
+ that silently: it covers the table's own borders and its cells' borders, and it
207
+ stops the moment the markup specifies any styling of its own — style one cell
208
+ and you have taken responsibility for all of them.
209
+
210
+ So write the house shape explicitly whenever the table is a new one, and let
211
+ inheritance cover the ones you are editing in place. The house shape, which is
212
+ what the artist's own pages already look like:
213
+
214
+ - The table itself: `<table border="1" style="border-collapse:collapse">`. The
215
+ `border` attribute is the part OneNote acts on — a CSS `border:1px solid` in
216
+ the style alone comes back as `border:0px`.
217
+ - Every cell: `<td style="border:1px solid #A3A3A3;padding:4px">`, with the
218
+ content in a `<p>`.
219
+ - The header row, where the table has one, additionally carries
220
+ `background-color:#EFEFEF` on each cell and wraps its text in `<b>`. A roster
221
+ of musicians, a milestones table, a Field/Value block — the first row names the
222
+ columns and is shaded so it reads as a header rather than as data.
223
+ - A free-text section is a single-cell table and has **no** header row. Shading
224
+ its only cell would make the whole section look like a heading.
225
+ - Column widths are not set. OneNote sizes them, and a width guessed from a
226
+ preview is worse than none.
227
+
228
+ Send this through the `html` parameter, never `text` — a table put through
229
+ `text` arrives entity-escaped and lands on the page as literal angle brackets.
230
+
231
+ Where a page needs several changes, give them as `changes` in one call. They are
232
+ previewed together, confirmed together and written together, and if any one of
233
+ them cannot be applied then none of them is. Changing one thing at a time makes
234
+ the tool re-read the page between every change.
235
+
236
+ Reading the page back afterwards is still worth doing on anything structural. It
237
+ is no longer a way of telling a display artifact from a real mangling — the
238
+ preview shows paragraph breaks and table rows as they are — but it is the only
239
+ thing that confirms what the page now holds.
240
+
178
241
  ## Boundaries
179
242
 
180
243
  - Never claim a page was changed unless the tool reported changing it. A patch
@@ -47,7 +47,7 @@
47
47
  "name": "Patch",
48
48
  "description": "Once the musician has decided something, hand them the smallest fragment that records it, and say which part of the page it goes under.",
49
49
  "file": ".artist/policies/PATCH.md",
50
- "sha256": "d844496f2720befd02efa0ae940355e569acf2467e18906bad7e0769350653e2"
50
+ "sha256": "47029fd651ab2458ecc4b60dd73b16decf3f5c5506f20f4f61a71ca69f3f38e0"
51
51
  },
52
52
  {
53
53
  "id": "project-type:concert",
package/dist/api.d.ts CHANGED
@@ -109,4 +109,5 @@ export declare const onenotePatchPage: (pageId: string, commands: readonly {
109
109
  target: string;
110
110
  action: string;
111
111
  content: string;
112
+ position?: "before" | "after";
112
113
  }[], token: string, etag: string | null) => Promise<void>;
@@ -66,8 +66,10 @@ export declare const extractElement: (html: string, attr: "id" | "data-id", valu
66
66
  export declare const preImage: (html: string, generatedId: string) => string;
67
67
  export type PatchCommand = {
68
68
  target: string;
69
- action: 'append' | 'replace';
69
+ action: 'append' | 'replace' | 'insert';
70
70
  content: string;
71
+ /** Only an insert has one. Graph ignores it elsewhere rather than refusing. */
72
+ position?: 'before' | 'after';
71
73
  };
72
74
  /**
73
75
  * Append to a container.
@@ -86,6 +88,20 @@ export declare const appendCommand: (target: string, contentHtml: string) => Pat
86
88
  * confusingly or hits something other than what was meant.
87
89
  */
88
90
  export declare const replaceCommand: (generatedId: string, contentHtml: string) => PatchCommand;
91
+ /**
92
+ * Put a new element beside an existing one.
93
+ *
94
+ * Graph lists `insert` for a `table` with a `position` of `before` or `after`,
95
+ * and this is the only way a new block reaches the middle of a page: `append`
96
+ * can only reach the end of the body, so without a sibling insert a section
97
+ * added to a template lands after everything, which for these pages is after
98
+ * the signature block rather than where it belongs.
99
+ *
100
+ * The anchor is named by its generated id for the same reason a replace is —
101
+ * `data-id` targets resolve for `append` and not for this, and the failure is
102
+ * a `20134` that reads as though the element could not be edited.
103
+ */
104
+ export declare const insertCommand: (generatedId: string, position: "before" | "after", contentHtml: string) => PatchCommand;
89
105
  /**
90
106
  * Plain text as a single paragraph, which is what replaces a paragraph.
91
107
  *
@@ -95,14 +111,71 @@ export declare const replaceCommand: (generatedId: string, contentHtml: string)
95
111
  * decided rather than smuggled in through the content string.
96
112
  */
97
113
  export declare const paragraph: (text: string) => string;
114
+ /**
115
+ * Well-formed, allowed, and balanced — or nothing is sent.
116
+ *
117
+ * Returns the markup unchanged on success. It is deliberately not a normaliser:
118
+ * what goes to Graph is byte-for-byte what was hashed into the confirmation
119
+ * token and shown in the preview, so there is no room for a rewrite between
120
+ * the musician's yes and the write.
121
+ */
122
+ export declare const validateFragment: (html: string) => string;
123
+ /**
124
+ * The tag of the single element a fragment consists of, or undefined if it is
125
+ * not exactly one element.
126
+ *
127
+ * A replace must yield an element of the kind the target supports: a table
128
+ * replaced by two tables, or by a table and a stray paragraph, is not the
129
+ * change that was previewed as one table.
130
+ */
131
+ export declare const rootTag: (html: string) => string | undefined;
132
+ export type EditablePart = {
133
+ element_id: string;
134
+ /** A table is replaced whole and with HTML; everything else takes plain text. */
135
+ kind: 'text' | 'table';
136
+ /** As the musician reads it: a table as its rows, anything else as its text. */
137
+ text: string;
138
+ /** The table this sits in, if it does. Null for a top-level element. */
139
+ inside_table: string | null;
140
+ };
141
+ /** One change. A draft carries a list of these, even when the list has one entry. */
142
+ export type EditChange = {
143
+ action: 'append' | 'replace' | 'insert';
144
+ /** The generated id of the element to replace, or to sit beside. Absent for an append. */
145
+ element_id: string | null;
146
+ /** Plain text, escaped on the way out. Null when the change is markup. */
147
+ text: string | null;
148
+ /** Validated markup. Null when the change is plain text. */
149
+ html: string | null;
150
+ /** Which side of the anchor an insert lands on. Null otherwise. */
151
+ position: 'before' | 'after' | null;
152
+ };
98
153
  export type EditDraft = {
99
154
  page_id: string;
100
- action: 'append' | 'replace';
101
- /** The generated id of the element to replace. Absent for an append. */
102
- element_id: string | null;
103
- text: string;
155
+ changes: EditChange[];
104
156
  source_page: string | null;
105
157
  };
158
+ export declare const inherit: (targetHtml: string, contentHtml: string) => {
159
+ html: string;
160
+ notes: string[];
161
+ };
162
+ /**
163
+ * A page and the changes to make to it, whether that is one or several.
164
+ *
165
+ * Several ride in one `PATCH`, which is what Graph's array of change objects is
166
+ * for and which this used to send one command at a time. The reason to batch is
167
+ * not only the round trips: generated ids move after *every* write, so eight
168
+ * changes applied one by one means eight reads, and every one of them is a
169
+ * chance for the page to have moved under the sequence. One command array
170
+ * resolves every target against a single read.
171
+ *
172
+ * That was verified rather than assumed, and so was the thing that makes it
173
+ * safe to approve as one step: a batch containing one unresolvable target
174
+ * applies **nothing** — `400`, and the page untouched. See
175
+ * scripts/spike-onenote-batch.mjs. A batch that half-applied would be a page in
176
+ * a state nobody previewed and nobody agreed to, and this capability would not
177
+ * be worth having.
178
+ */
106
179
  export declare const editFrom: (params: Record<string, unknown>) => EditDraft;
107
180
  /**
108
181
  * The token a preview returns and an edit must carry back.
@@ -117,7 +190,60 @@ export declare const editFrom: (params: Record<string, unknown>) => EditDraft;
117
190
  * Hashing the pre-image makes that case fail closed: the edit is refused unless
118
191
  * the element still holds exactly what the musician was shown.
119
192
  */
120
- export declare const editToken: (draft: EditDraft, preImageHtml: string) => Promise<string>;
193
+ export declare const editToken: (draft: EditDraft, preImages: string[]) => Promise<string>;
194
+ /** A table as its cell text, row by row. */
195
+ export declare const tableRows: (tableHtml: string) => string[][];
196
+ /**
197
+ * A table as something a musician can read and say yes or no to.
198
+ *
199
+ * This is the difference between a whole-table replace being previewable and
200
+ * not. The thing a replace destroys is a block of markup hundreds of characters
201
+ * wide; shown raw it is not a preview, it is a wall that gets approved unread —
202
+ * and an approval nobody could read is exactly the failure the confirmation
203
+ * step exists to prevent. So the table is shown as its rows.
204
+ *
205
+ * **Nothing is ever cut.** This originally clipped a cell at forty characters
206
+ * with an ellipsis, on the reasoning that the shape of the table is what a
207
+ * reader checks. That was wrong in the case these pages are mostly made of: a
208
+ * single-cell table holding a paragraph of free text, where the whole change
209
+ * lives past the fortieth character. Both halves of the preview then rendered
210
+ * to the same truncated line, and the preview asserted in effect that a
211
+ * destructive change was a no-op. Found in use, not by any test here — the test
212
+ * asserted the ellipsis was present, which is how a defect gets a green tick.
213
+ *
214
+ * So a long cell wraps within its column instead. A wrapped preview is longer
215
+ * to read; a truncated one cannot be read at all.
216
+ *
217
+ * `marks` optionally prefixes each row, from `markRows`. The mark sits outside
218
+ * the table so the columns still line up under it.
219
+ */
220
+ export declare const renderTable: (tableHtml: string, marks?: string[]) => string;
221
+ export declare const renderRows: (rows: string[][], marks?: string[]) => string;
222
+ /**
223
+ * Which rows a replace actually changes, matched up rather than compared by
224
+ * position.
225
+ *
226
+ * A row inserted near the top shifts every row under it, and comparing by index
227
+ * would then mark the whole table as changed — which tells a reader as little
228
+ * as marking none of it. So the two row lists are matched by their longest
229
+ * common subsequence, and only what is genuinely new, gone or altered is
230
+ * marked.
231
+ */
232
+ export declare const markRows: (before: string[][], after: string[][]) => {
233
+ before: string[];
234
+ after: string[];
235
+ };
236
+ /**
237
+ * A fragment of several elements, each rendered as what it is.
238
+ *
239
+ * A replacement may now be a paragraph, a heading and a table at once, and
240
+ * running the whole thing through `asText` turned the table back into a column
241
+ * of loose lines — the same flattening `copy-onenote-page.mjs` refuses for the
242
+ * same reason. The reader has to be able to see that a table is arriving.
243
+ */
244
+ export declare const renderFragment: (html: string) => string;
245
+ /** Whether a generated id names a table, which decides what may be done to it. */
246
+ export declare const isTableId: (id: string) => boolean;
121
247
  /**
122
248
  * What the page currently says, element by element, so a caller can name the
123
249
  * part to change without guessing an id.
@@ -127,13 +253,12 @@ export declare const editToken: (draft: EditDraft, preImageHtml: string) => Prom
127
253
  * quoted back in a later session — Microsoft says they "might change after a
128
254
  * page update", and that was observed rather than merely read.
129
255
  */
130
- export declare const readEditableParts: (token: string, pageId: string) => Promise<{
256
+ export declare const readEditableParts: (token: string, pageId: string, { full }?: {
257
+ full?: boolean;
258
+ }) => Promise<{
131
259
  html: string;
132
260
  etag: string | null;
133
- parts: {
134
- element_id: string;
135
- text: string;
136
- }[];
261
+ parts: EditablePart[];
137
262
  }>;
138
263
  /**
139
264
  * Show the change that would be made, and hand back the token making it needs.
@@ -143,33 +268,35 @@ export declare const readEditableParts: (token: string, pageId: string) => Promi
143
268
  export declare function previewEdit(token: string, params: Record<string, unknown>): Promise<{
144
269
  preview: null;
145
270
  page_id: string;
146
- parts: {
147
- element_id: string;
148
- text: string;
149
- }[];
271
+ parts: EditablePart[];
272
+ abbreviated: boolean;
150
273
  confirmation_token: null;
151
274
  note: string;
152
275
  } | {
153
276
  preview: string;
154
277
  page_id: string;
155
- parts: {
156
- element_id: string;
157
- text: string;
158
- }[];
278
+ parts: EditablePart[];
279
+ abbreviated: boolean;
159
280
  confirmation_token: string;
160
281
  note: string;
161
282
  }>;
162
283
  /**
163
- * Apply the change, having shown it.
284
+ * Apply the change, or the batch of them, having shown it.
164
285
  *
165
286
  * The page is read again here rather than trusted from the preview, and that is
166
- * not caution: generated ids move after any page update, so an id that was
167
- * right at preview may name something else by now. Re-reading also produces the
168
- * ETag that goes out as `If-Match`, which is what makes the musician typing in
287
+ * not caution: generated ids move after any page update, so an id that was right
288
+ * at preview may name something else by now. Re-reading also produces the ETag
289
+ * that goes out as `If-Match`, which is what makes the musician typing in
169
290
  * OneNote between the two calls a refusal rather than a silent overwrite.
170
291
  *
171
- * The confirmation token is checked against the payload *and* the pre-image, so
172
- * an element whose content changed since the preview fails closed.
292
+ * The confirmation token is checked against every change *and* every pre-image,
293
+ * so a batch fails closed if any element it would overwrite has changed since
294
+ * the preview — not merely the one that moved.
295
+ *
296
+ * All the commands go out in one `PATCH`, which is what Graph's array is for.
297
+ * A batch containing an unresolvable target applies nothing at all, verified
298
+ * against a real page, so there is no state in which half a confirmed batch is
299
+ * on the page.
173
300
  */
174
301
  export declare function applyEdit(token: string, params: Record<string, unknown>, record: RecordWrite): Promise<{
175
302
  changed: boolean;