@jini-ai/vibecoding 0.4.2 → 0.4.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.3 — 2026-10-22
4
+
5
+ - 2026-10-22: Region parser and session refactors, coverage.
6
+
3
7
  ## 0.4.1
4
8
 
5
9
  ### Patch Changes
@@ -108,14 +108,41 @@ function* walkElements(node) {
108
108
  for (const child of children)
109
109
  yield* walkElements(child);
110
110
  }
111
- /** Fresh `<div>` fragment-context element — see this module's doc for why `<div>`, not the default. */
112
- function divFragmentContext() {
113
- return defaultTreeAdapter.createElement("div", html.NS.HTML, []);
114
- }
115
111
  /** A parse-error entry, phrased for the model rather than left as parse5's bare kebab-case code. */
116
112
  function describeParseError(error) {
117
113
  return `${error.code.replace(/-/g, " ")} near character ${error.startOffset}`;
118
114
  }
115
+ function analyzeRegion({ element, handle }, _options = {}) {
116
+ const location = element.sourceCodeLocation;
117
+ if (location === null || location === undefined || location.startTag === undefined) {
118
+ return {
119
+ locationProblem: `a "${AGENT_ELEMENT_ATTRIBUTE}" region ("${handle}") could not be located in the document — ` +
120
+ "the parser's error-recovery rules likely dropped or repositioned it. Rewrite the document " +
121
+ "so this element is reachable in ordinary document structure (for example, not a table-only " +
122
+ "tag used outside a <table>).",
123
+ };
124
+ }
125
+ if (location.endTag === undefined) {
126
+ return {
127
+ locationProblem: VOID_ELEMENTS.has(element.tagName)
128
+ ? `"${handle}" tags a <${element.tagName}> element, which is void and has no inner content — ` +
129
+ `void elements cannot be a ${AGENT_ELEMENT_ATTRIBUTE} region`
130
+ : `a "${AGENT_ELEMENT_ATTRIBUTE}" region ("${handle}") has no closing tag the parser could ` +
131
+ "locate — its content boundaries cannot be determined safely. Close the tag explicitly.",
132
+ };
133
+ }
134
+ const role = attributeValue(element, "data-agent-role");
135
+ const label = attributeValue(element, "data-agent-label");
136
+ return {
137
+ region: {
138
+ handle,
139
+ innerStart: location.startTag.endOffset,
140
+ innerEnd: location.endTag.startOffset,
141
+ ...(role === undefined ? {} : { role }),
142
+ ...(label === undefined ? {} : { label }),
143
+ },
144
+ };
145
+ }
119
146
  /**
120
147
  * One parse pass, shared by `findRegions` and `checkWellFormed` so both methods judge the same
121
148
  * parse5 run rather than parsing the document twice with a chance of disagreeing.
@@ -124,7 +151,9 @@ function describeParseError(error) {
124
151
  */
125
152
  function analyzeDocument(html_) {
126
153
  const parseErrorReasons = [];
127
- const fragment = parseFragment(divFragmentContext(), html_, {
154
+ /** Fresh `<div>` fragment-context element — see this module's doc for why `<div>`, not the default. */
155
+ const context = defaultTreeAdapter.createElement("div", html.NS.HTML, []);
156
+ const fragment = parseFragment(context, html_, {
128
157
  sourceCodeLocationInfo: true,
129
158
  onParseError: (error) => parseErrorReasons.push(describeParseError(error)),
130
159
  });
@@ -132,38 +161,14 @@ function analyzeDocument(html_) {
132
161
  let locationProblem;
133
162
  for (const element of walkElements(fragment)) {
134
163
  const handle = attributeValue(element, AGENT_ELEMENT_ATTRIBUTE);
135
- if (handle === undefined)
136
- continue;
137
- if (locationProblem !== undefined)
164
+ if (handle === undefined || locationProblem !== undefined)
138
165
  continue; // Already failed; stop accumulating regions.
139
- const location = element.sourceCodeLocation;
140
- if (location === null || location === undefined || location.startTag === undefined) {
141
- locationProblem =
142
- `a "${AGENT_ELEMENT_ATTRIBUTE}" region ("${handle}") could not be located in the document — ` +
143
- "the parser's error-recovery rules likely dropped or repositioned it. Rewrite the document " +
144
- "so this element is reachable in ordinary document structure (for example, not a table-only " +
145
- "tag used outside a <table>).";
166
+ const analyzed = analyzeRegion({ element, handle });
167
+ if ("locationProblem" in analyzed) {
168
+ locationProblem = analyzed.locationProblem;
146
169
  continue;
147
170
  }
148
- if (location.endTag === undefined) {
149
- locationProblem = VOID_ELEMENTS.has(element.tagName)
150
- ? `"${handle}" tags a <${element.tagName}> element, which is void and has no inner content — ` +
151
- `void elements cannot be a ${AGENT_ELEMENT_ATTRIBUTE} region`
152
- : `a "${AGENT_ELEMENT_ATTRIBUTE}" region ("${handle}") has no closing tag the parser could ` +
153
- "locate — its content boundaries cannot be determined safely. Close the tag explicitly.";
154
- continue;
155
- }
156
- regions.push({
157
- handle,
158
- innerStart: location.startTag.endOffset,
159
- innerEnd: location.endTag.startOffset,
160
- ...(attributeValue(element, "data-agent-role") === undefined
161
- ? {}
162
- : { role: attributeValue(element, "data-agent-role") }),
163
- ...(attributeValue(element, "data-agent-label") === undefined
164
- ? {}
165
- : { label: attributeValue(element, "data-agent-label") }),
166
- });
171
+ regions.push(analyzed.region);
167
172
  }
168
173
  if (locationProblem === undefined) {
169
174
  const rawMentions = html_.match(RAW_HANDLE_ATTRIBUTE_RE)?.length ?? 0;
@@ -61,6 +61,13 @@ export function createVibecodingSession({ target }, options = {}) {
61
61
  function cacheChange(change, side) {
62
62
  partContent.set(change.id, side === 'after' ? change.after : change.before);
63
63
  }
64
+ /** Mirrors a recorded snapshot restore in the cache and re-reads the part list it may have grown. */
65
+ async function restoredPatch(entry) {
66
+ for (const change of entry.changes)
67
+ cacheChange(change, 'after');
68
+ const parts = await target.listParts();
69
+ return { parts, partContent: new Map(partContent), canUndo: history.canUndo(), canRedo: history.canRedo() };
70
+ }
64
71
  return {
65
72
  target,
66
73
  history,
@@ -140,20 +147,8 @@ export function createVibecodingSession({ target }, options = {}) {
140
147
  takeSnapshot: () => run(async () => ({ result: await target.snapshot(), patch: {} })),
141
148
  restoreSnapshot: ({ snapshot }) => run(async () => {
142
149
  const entry = await history.restore({ snapshot });
143
- if (!entry)
144
- return { result: entry, patch: {} };
145
- for (const change of entry.changes)
146
- cacheChange(change, 'after');
147
- const parts = await target.listParts();
148
- return {
149
- result: entry,
150
- patch: {
151
- parts,
152
- partContent: new Map(partContent),
153
- canUndo: history.canUndo(),
154
- canRedo: history.canRedo(),
155
- },
156
- };
150
+ // A restore that already matched records no entry and changes nothing to mirror.
151
+ return { result: entry, patch: entry ? await restoredPatch(entry) : {} };
157
152
  }),
158
153
  };
159
154
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/vibecoding",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Conversational artifact authoring: a target-agnostic edit loop — addressable parts, validated replace, operation-level undo/redo, and per-turn snapshot/rewind. Framework-free in `/core`; tagged-region HTML documents behind ./html; filesystem targets behind ./node and the chat/preview surface behind ./react.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -75,10 +75,10 @@
75
75
  "typescript": "^5.9.2",
76
76
  "vitest": "^3.2.4",
77
77
  "parse5": "^8.0.1",
78
- "@jini-ai/core": "0.4.2"
78
+ "@jini-ai/core": "0.4.9"
79
79
  },
80
80
  "dependencies": {
81
- "@jini-ai/core": "^0.4.2"
81
+ "@jini-ai/core": "^0.4.9"
82
82
  },
83
83
  "peerDependencies": {
84
84
  "react": "^18.3.0 || ^19.0.0",