markform 0.1.24 → 0.1.25

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.
Files changed (44) hide show
  1. package/README.md +54 -31
  2. package/dist/ai-sdk.d.mts +1 -1
  3. package/dist/ai-sdk.mjs +2 -2
  4. package/dist/bin.mjs +1 -1
  5. package/dist/{cli-B1DhFYBS.mjs → cli-B1T8kMFt.mjs} +85 -40
  6. package/dist/cli-B1T8kMFt.mjs.map +1 -0
  7. package/dist/cli.mjs +1 -1
  8. package/dist/{coreTypes-GxzWNXap.d.mts → coreTypes-CxpqKpBA.d.mts} +45 -2
  9. package/dist/{coreTypes-CctFK6uE.mjs → coreTypes-DIv9Aabl.mjs} +19 -5
  10. package/dist/coreTypes-DIv9Aabl.mjs.map +1 -0
  11. package/dist/{fillRecord-DeqI2pQ5.d.mts → fillRecord-V3vlyobd.d.mts} +5 -1
  12. package/dist/{fillRecordRenderer-VBQ2vwPV.mjs → fillRecordRenderer-BqRPHPmE.mjs} +47 -15
  13. package/dist/fillRecordRenderer-BqRPHPmE.mjs.map +1 -0
  14. package/dist/index.d.mts +32 -4
  15. package/dist/index.mjs +4 -4
  16. package/dist/{prompts-BCnYaH4_.mjs → prompts-DaPKumGY.mjs} +114 -11
  17. package/dist/prompts-DaPKumGY.mjs.map +1 -0
  18. package/dist/render.d.mts +2 -2
  19. package/dist/render.mjs +1 -1
  20. package/dist/{session-BLjN3BkJ.mjs → session-BW9jtYNV.mjs} +2 -2
  21. package/dist/{session-BLjN3BkJ.mjs.map → session-BW9jtYNV.mjs.map} +1 -1
  22. package/dist/{session-D7C7IlEv.mjs → session-DHyTMP67.mjs} +1 -1
  23. package/dist/{shared-DtorFV21.mjs → shared-BLh342F5.mjs} +1 -1
  24. package/dist/{shared-CuSRYcIB.mjs → shared-BszoSkAO.mjs} +8 -8
  25. package/dist/{shared-CuSRYcIB.mjs.map → shared-BszoSkAO.mjs.map} +1 -1
  26. package/dist/{src-C5OWf1dL.mjs → src-DrXmaOWl.mjs} +155 -27
  27. package/dist/src-DrXmaOWl.mjs.map +1 -0
  28. package/docs/markform-apis.md +19 -7
  29. package/docs/markform-reference.md +247 -178
  30. package/docs/markform-spec.md +81 -33
  31. package/docs/skill/SKILL.md +62 -20
  32. package/examples/markform-demo-playbook.md +342 -0
  33. package/examples/parallel/parallel-research.form.md +2 -6
  34. package/examples/simple/simple-mock-filled.report.md +2 -2
  35. package/examples/simple/simple-skipped-filled.report.md +2 -2
  36. package/examples/twitter-thread/twitter-thread.form.md +5 -5
  37. package/package.json +1 -1
  38. package/dist/cli-B1DhFYBS.mjs.map +0 -1
  39. package/dist/coreTypes-CctFK6uE.mjs.map +0 -1
  40. package/dist/fillRecordRenderer-VBQ2vwPV.mjs.map +0 -1
  41. package/dist/prompts-BCnYaH4_.mjs.map +0 -1
  42. package/dist/src-C5OWf1dL.mjs.map +0 -1
  43. package/examples/startup-research/startup-research-mock-filled.form.md +0 -297
  44. package/examples/startup-research/startup-research.form.md +0 -181
package/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # Markform
2
2
 
3
- [![CI](https://github.com/jlevy/markform/actions/workflows/ci.yml/badge.svg)](https://github.com/jlevy/markform/actions/runs/22024451235)
4
- [![Coverage](https://raw.githubusercontent.com/jlevy/markform/main/badges/packages/markform/coverage-total.svg)](https://github.com/jlevy/markform/actions/runs/22024451235)
3
+ [![Follow @ojoshe on X](https://img.shields.io/badge/follow_%40ojoshe-black?logo=x&logoColor=white)](https://x.com/ojoshe)
4
+ [![CI](https://github.com/jlevy/markform/actions/workflows/ci.yml/badge.svg)](https://github.com/jlevy/markform/actions/workflows/ci.yml)
5
5
  [![npm version](https://img.shields.io/npm/v/markform)](https://www.npmjs.com/package/markform)
6
- [![X Follow](https://img.shields.io/twitter/follow/ojoshe)](https://x.com/ojoshe)
7
6
 
8
7
  ### What if your Markdown docs had an agent-friendly semantic API?
9
8
 
@@ -17,10 +16,10 @@ Humans can review or intervene at any point.
17
16
 
18
17
  ### Why forms?
19
18
 
20
- For deep research or complex AI tasks, you need more than just prompts or
21
- flow: you need *structure*, which is precise control over agent output at every stage of
22
- a workflow. A well-designed form combines instructions, structured data, and validations
23
- in one place.
19
+ For deep research or complex AI tasks, you need more than just prompts or flow: you need
20
+ *structure*, which is precise control over agent output at every stage of a workflow.
21
+ A well-designed form combines instructions, structured data, and validations in one
22
+ place.
24
23
 
25
24
  ### How it Works
26
25
 
@@ -42,20 +41,24 @@ in one place.
42
41
 
43
42
  ### Useful details
44
43
 
45
- - Markform syntax is a good source format: it is **token-efficient text** you can read, diff, and
46
- version control and it is **ideal for context engineering** because it combines
47
- document context, data schema, and memory (data filled so far).
44
+ - Markform syntax is a good source format: it is **token-efficient text** you can read,
45
+ diff, and version control and it is **ideal for context engineering** because it
46
+ combines document context, data schema, and memory (data filled so far).
48
47
 
49
- - Structure is defined with HTML comment tags (`<!-- field -->`) that
50
- render invisibly on GitHub, so **forms look like regular Markdown**. (Jinja-style
51
- tag syntax also works if you prefer.)
48
+ - Structure is defined with HTML comment tags (`<!-- field -->`) that render invisibly
49
+ on GitHub, so **forms look like regular Markdown**. (Jinja-style tag syntax also works
50
+ if you prefer.)
52
51
 
53
- - Optionally, **a fill record** of the form-filling process is kept, so you can see
54
- and debug exactly how forms are filled by agents, tool usage, LLM call time, etc.
52
+ - Form-filling can scale to **hundreds of fields** in a form filled by **dozens of
53
+ concurrent LLM requests** which makes it possible to systemetize large, complex
54
+ research processes or workflows.
55
+ (And **a fill record** of the form-filling process is kept, so you can see and debug
56
+ exactly how forms are filled by agents, tool usage, LLM call time, etc.)
55
57
 
56
- - The CLI has a built-in web renderer, **`markform serve`**, for easy viewing and debugging
57
- of forms (including a form web UI, the form schema, and a waterfall-style overview of the
58
- fill record, including performance details, which is useful for large, concurrently filled forms).
58
+ - The CLI has a built-in web renderer, **`markform serve`**, for easy viewing and
59
+ debugging of forms (including a form web UI, the form schema, and a waterfall-style
60
+ overview of the fill record, including performance details, which is useful for large,
61
+ concurrently filled forms).
59
62
 
60
63
  ## Simple Example: Research a Movie
61
64
 
@@ -302,11 +305,34 @@ See [the FAQ](#faq) for more on the design.
302
305
 
303
306
  ## Quick Start
304
307
 
308
+ There are three ways to get started, from quickest to most thorough:
309
+
310
+ **1. Run an example automatically** — copy bundled forms and let an LLM fill one:
311
+
305
312
  ```bash
306
- # Copy example forms to ./forms/ and run one interactively.
307
- # Set OPENAI_API_KEY or ANTHROPIC_API_KEY (or put in .env) for research examples
313
+ # Set OPENAI_API_KEY or ANTHROPIC_API_KEY (or put in .env)
308
314
  npx markform@latest examples
315
+ ```
316
+
317
+ Pick `movie-research-demo.form.md` for a quick demo.
318
+
319
+ **2. Agent-guided tour** — ask a coding agent (like Claude Code) to walk you through a
320
+ specific example step by step.
321
+ The agent copies the form, explains the structure, fills fields, validates, and exports:
322
+
323
+ ```bash
324
+ # List available examples
325
+ markform examples --list
326
+ # Copy one to work with
327
+ markform examples --name movie-research-demo
328
+ ```
309
329
 
330
+ **3. End-to-end walkthrough** — have a coding agent follow the
331
+ [demo playbook](packages/markform/examples/markform-demo-playbook.md) to design a
332
+ research form from scratch, fill it with real data, validate, export, and browse.
333
+ This is the most thorough tour of all Markform features.
334
+
335
+ ```bash
310
336
  # Read the docs (tell your agents to run these; they are agent-friendly!)
311
337
  npx markform # CLI help
312
338
  npx markform readme # This file
@@ -314,10 +340,6 @@ npx markform docs # Quick reference for writing Markforms
314
340
  npx markform spec # Read the full spec
315
341
  ```
316
342
 
317
- The `markform examples` command copies some sample forms to `./forms` and prompts you to
318
- fill in a form interactively and then optionally have an agent complete it.
319
- Pick `movie-research-demo.form.md` for a quick example.
320
-
321
343
  ## Installation
322
344
 
323
345
  Requires Node.js 20+.
@@ -333,8 +355,9 @@ npm install markform
333
355
  ### Use as a Claude Code Skill
334
356
 
335
357
  If you install markform globally (`npm install -g markform`), you can tell Claude to run
336
- `markform setup --auto` to install it as a Claude Code skill. This teaches Claude how to
337
- use markform commands when working with `.form.md` files in your project.
358
+ `markform setup --auto` to install it as a Claude Code skill.
359
+ This teaches Claude how to use markform commands when working with `.form.md` files in
360
+ your project.
338
361
 
339
362
  ```bash
340
363
  # Install as a Claude Code skill (non-interactive, for agents)
@@ -431,7 +454,7 @@ flowchart LR
431
454
  subgraph ENGINE["<b>CORE TYPESCRIPT APIS</b><br/>Markdoc parser, serializer,<br/>patch application,<br/>validation (jiti for rules)"]
432
455
  end
433
456
 
434
- subgraph TEST["<b>TESTING FRAMEWORK</b><br/>Golden session testing<br/>(.session.yaml transcripts)"]
457
+ subgraph PARSER["<b>PARSER</b><br/>Markform language<br/>parser/serializer"]
435
458
  end
436
459
 
437
460
  CLI --> ENGINE
@@ -439,7 +462,7 @@ flowchart LR
439
462
  AGENT --> HARNESS
440
463
  AGENT --> ENGINE
441
464
  HARNESS --> ENGINE
442
- ENGINE --> TEST
465
+ ENGINE --> PARSER
443
466
  end
444
467
 
445
468
  SPEC ~~~ IMPL
@@ -454,7 +477,7 @@ flowchart LR
454
477
  style CLI fill:#ffe8cc,stroke:#fb8500
455
478
  style AGENT fill:#ffe8cc,stroke:#fb8500
456
479
  style HARNESS fill:#ffe8cc,stroke:#fb8500
457
- style TEST fill:#ffe8cc,stroke:#fb8500
480
+ style PARSER fill:#ffe8cc,stroke:#fb8500
458
481
  ```
459
482
 
460
483
  ## CLI Commands
@@ -588,8 +611,8 @@ If unsure, try `gpt-5-mini` first as it’s fast and supports web search.
588
611
 
589
612
  ## Programmatic Usage
590
613
 
591
- Markform exports a parsing engine, rendering functions, and AI SDK integration for use in
592
- your own applications.
614
+ Markform exports a parsing engine, rendering functions, and AI SDK integration for use
615
+ in your own applications.
593
616
 
594
617
  ### Basic Parsing
595
618
 
package/dist/ai-sdk.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { At as ParsedForm, Nt as PatchSchema, U as FieldResponse, Y as FormSchema, at as InspectResult, et as Id, gr as ValidatorRegistry, jt as Patch, r as ApplyResult } from "./coreTypes-GxzWNXap.mjs";
2
+ import { At as ParsedForm, Nt as PatchSchema, U as FieldResponse, Y as FormSchema, at as InspectResult, et as Id, gr as ValidatorRegistry, jt as Patch, r as ApplyResult } from "./coreTypes-CxpqKpBA.mjs";
3
3
  import { z } from "zod";
4
4
 
5
5
  //#region src/integrations/toolTypes.d.ts
package/dist/ai-sdk.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
- import { R as PatchSchema } from "./coreTypes-CctFK6uE.mjs";
3
- import { _ as inspect, m as applyPatches, p as findFieldById, s as getPatchFormatHint, u as getFieldIdFromRef, w as serializeForm } from "./prompts-BCnYaH4_.mjs";
2
+ import { R as PatchSchema } from "./coreTypes-DIv9Aabl.mjs";
3
+ import { _ as inspect, m as applyPatches, p as findFieldById, s as getPatchFormatHint, u as getFieldIdFromRef, w as serializeForm } from "./prompts-DaPKumGY.mjs";
4
4
  import { z } from "zod";
5
5
 
6
6
  //#region src/integrations/vercelAiSdkTools.ts
package/dist/bin.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { t as runCli } from "./cli-B1DhFYBS.mjs";
3
+ import { t as runCli } from "./cli-B1T8kMFt.mjs";
4
4
  import { resolve } from "node:path";
5
5
  import { existsSync } from "node:fs";
6
6
  import { config } from "dotenv";
@@ -1,10 +1,10 @@
1
1
 
2
- import { R as PatchSchema } from "./coreTypes-CctFK6uE.mjs";
3
- import { $ as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN, E as serializeReport, H as AGENT_ROLE, K as DEFAULT_MAX_PATCHES_PER_TURN, Q as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, S as computeStructureSummary, T as serializeRawMarkdown, U as DEFAULT_FORMS_DIR, W as DEFAULT_MAX_ISSUES_PER_TURN, X as DEFAULT_PORT, Y as DEFAULT_MAX_TURNS, _ as inspect, _t as parseModelIdForDisplay, at as deriveExportPath, c as filterIssuesByOrder, ct as deriveSchemaPath, d as coerceInputContext, f as coerceToFieldPatch, ft as SUGGESTED_LLMS, gt as hasWebSearchSupport, h as getAllFields, it as USER_ROLE, k as validateSyntaxConsistency, l as filterIssuesByScope, lt as detectFileType, m as applyPatches, mt as formatSuggestedLlms, nt as MAX_FORMS_IN_MENU, ot as deriveFillRecordPath, p as findFieldById, pt as WEB_SEARCH_CONFIG, rt as REPORT_EXTENSION, st as deriveReportPath, u as getFieldIdFromRef, ut as parseRolesFlag, w as serializeForm, x as computeProgressSummary } from "./prompts-BCnYaH4_.mjs";
4
- import { C as getProviderInfo, D as createLiveAgent, E as buildMockWireFormat, H as parseForm, N as createHarness, O as FillRecordCollector, T as resolveModel, V as formToJsonSchema, _ as resolveHarnessConfig, g as formatFillRecordSummary, h as stripUnstableFillRecordFields, i as runResearch, j as createMockAgent, k as computeExecutionPlan, m as isEmptyFillRecord, n as isResearchForm, t as VERSION, v as fillForm, w as getProviderNames } from "./src-C5OWf1dL.mjs";
5
- import { n as serializeSession } from "./session-BLjN3BkJ.mjs";
6
- import { _ as writeFile$1, a as formatPath, c as logError, d as logTiming, f as logVerbose, g as stripHtmlComments, h as shouldUseColors, i as formatOutput, l as logInfo, m as readFile$1, n as createSpinner, o as getCommandContext, p as logWarn, r as ensureFormsDir, s as logDryRun, t as OUTPUT_FORMATS, u as logSuccess } from "./shared-CuSRYcIB.mjs";
7
- import { a as renderJsonContent, c as renderViewContent, i as highlightYamlValue, l as renderYamlContent, o as renderMarkdownContent, r as renderFillRecordContent, s as renderSourceContent, u as escapeHtml } from "./fillRecordRenderer-VBQ2vwPV.mjs";
2
+ import { R as PatchSchema } from "./coreTypes-DIv9Aabl.mjs";
3
+ import { $ as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN, E as serializeReport, H as AGENT_ROLE, K as DEFAULT_MAX_PATCHES_PER_TURN, Q as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, S as computeStructureSummary, T as serializeRawMarkdown, U as DEFAULT_FORMS_DIR, W as DEFAULT_MAX_ISSUES_PER_TURN, X as DEFAULT_PORT, Y as DEFAULT_MAX_TURNS, _ as inspect, _t as parseModelIdForDisplay, at as deriveExportPath, c as filterIssuesByOrder, ct as deriveSchemaPath, d as coerceInputContext, f as coerceToFieldPatch, ft as SUGGESTED_LLMS, gt as hasWebSearchSupport, h as getAllFields, it as USER_ROLE, k as validateSyntaxConsistency, l as filterIssuesByScope, lt as detectFileType, m as applyPatches, mt as formatSuggestedLlms, nt as MAX_FORMS_IN_MENU, ot as deriveFillRecordPath, p as findFieldById, pt as WEB_SEARCH_CONFIG, rt as REPORT_EXTENSION, st as deriveReportPath, u as getFieldIdFromRef, ut as parseRolesFlag, w as serializeForm, x as computeProgressSummary } from "./prompts-DaPKumGY.mjs";
4
+ import { C as getProviderInfo, D as createLiveAgent, E as buildMockWireFormat, H as parseForm, N as createHarness, O as FillRecordCollector, T as resolveModel, V as formToJsonSchema, _ as resolveHarnessConfig, g as formatFillRecordSummary, h as stripUnstableFillRecordFields, i as runResearch, j as createMockAgent, k as computeExecutionPlan, m as isEmptyFillRecord, n as isResearchForm, t as VERSION, v as fillForm, w as getProviderNames } from "./src-DrXmaOWl.mjs";
5
+ import { n as serializeSession } from "./session-BW9jtYNV.mjs";
6
+ import { _ as writeFile$1, a as formatPath, c as logError, d as logTiming, f as logVerbose, g as stripHtmlComments, h as shouldUseColors, i as formatOutput, l as logInfo, m as readFile$1, n as createSpinner, o as getCommandContext, p as logWarn, r as ensureFormsDir, s as logDryRun, t as OUTPUT_FORMATS, u as logSuccess } from "./shared-BszoSkAO.mjs";
7
+ import { a as renderJsonContent, c as renderViewContent, i as highlightYamlValue, l as renderYamlContent, o as renderMarkdownContent, r as renderFillRecordContent, s as renderSourceContent, u as escapeHtml } from "./fillRecordRenderer-BqRPHPmE.mjs";
8
8
  import Markdoc from "@markdoc/markdoc";
9
9
  import YAML from "yaml";
10
10
  import { Command } from "commander";
@@ -1026,6 +1026,12 @@ const EXAMPLE_DEFINITIONS = [
1026
1026
  path: "simple/simple.form.md",
1027
1027
  type: "fill"
1028
1028
  },
1029
+ {
1030
+ id: "twitter-thread",
1031
+ filename: "twitter-thread.form.md",
1032
+ path: "twitter-thread/twitter-thread.form.md",
1033
+ type: "fill"
1034
+ },
1029
1035
  {
1030
1036
  id: "movie-deep-research",
1031
1037
  filename: "movie-deep-research.form.md",
@@ -2436,18 +2442,38 @@ function registerRunCommand(program) {
2436
2442
  */
2437
2443
  /**
2438
2444
  * Print non-interactive list of examples.
2445
+ * Supports --format=json and --format=yaml via formatOutput().
2439
2446
  */
2440
- function printExamplesList() {
2441
- console.log(pc.bold("Available examples:\n"));
2447
+ function printExamplesList(ctx) {
2442
2448
  const examples = getAllExamplesWithMetadata();
2443
- for (const example of examples) {
2444
- const typeLabel = example.type === "research" ? pc.magenta("[research]") : pc.blue("[fill]");
2445
- console.log(` ${pc.cyan(example.id)} ${typeLabel}`);
2446
- console.log(` ${pc.bold(example.title ?? example.id)}`);
2447
- console.log(` ${example.description ?? "No description"}`);
2448
- console.log(` Source: ${formatPath(getExamplePath(example.id))}`);
2449
- console.log("");
2450
- }
2449
+ const output = formatOutput(ctx, examples.map((example) => ({
2450
+ id: example.id,
2451
+ filename: example.filename,
2452
+ type: example.type,
2453
+ title: example.title ?? example.id,
2454
+ description: example.description ?? ""
2455
+ })), (_data, useColors) => {
2456
+ const c = useColors ? pc : {
2457
+ bold: (s) => s,
2458
+ cyan: (s) => s,
2459
+ magenta: (s) => s,
2460
+ blue: (s) => s,
2461
+ dim: (s) => s
2462
+ };
2463
+ const lines = [c.bold("Available examples:"), ""];
2464
+ for (const example of examples) {
2465
+ const typeLabel = example.type === "research" ? c.magenta("[research]") : c.blue("[fill]");
2466
+ lines.push(` ${c.cyan(example.id)} ${typeLabel}`);
2467
+ lines.push(` ${c.bold(example.title ?? example.id)}`);
2468
+ lines.push(` ${example.description ?? "No description"}`);
2469
+ lines.push(` Source: ${formatPath(getExamplePath(example.id))}`);
2470
+ lines.push("");
2471
+ }
2472
+ lines.push(c.dim("Tip: For a comprehensive end-to-end walkthrough, ask your coding agent"));
2473
+ lines.push(c.dim("to run the Markform QA playbook (tests/qa/markform-full-walkthrough.qa.md)."));
2474
+ return lines.join("\n");
2475
+ });
2476
+ console.log(output);
2451
2477
  }
2452
2478
  /**
2453
2479
  * Copy an example form to the forms directory.
@@ -2588,7 +2614,7 @@ function registerExamplesCommand(program) {
2588
2614
  const ctx = getCommandContext(cmd);
2589
2615
  try {
2590
2616
  if (options.list) {
2591
- printExamplesList();
2617
+ printExamplesList(ctx);
2592
2618
  return;
2593
2619
  }
2594
2620
  const formsDir = getFormsDir(ctx.formsDir);
@@ -4115,13 +4141,18 @@ function openBrowser(url) {
4115
4141
  /**
4116
4142
  * Build tabs for a form file.
4117
4143
  * All tabs are always present - content is generated dynamically from the form.
4118
- * Tab order: View, Edit, Source, Report, Values, Schema, Fill Record (if sidecar exists)
4144
+ * Tab order: Form, Report, Edit, Source, Values, Schema, Fill Record (if sidecar exists)
4119
4145
  */
4120
4146
  function buildFormTabs(formPath) {
4121
4147
  const tabs = [
4122
4148
  {
4123
4149
  id: "view",
4124
- label: "View",
4150
+ label: "Form",
4151
+ path: null
4152
+ },
4153
+ {
4154
+ id: "report",
4155
+ label: "Report",
4125
4156
  path: null
4126
4157
  },
4127
4158
  {
@@ -4134,11 +4165,6 @@ function buildFormTabs(formPath) {
4134
4165
  label: "Source",
4135
4166
  path: formPath
4136
4167
  },
4137
- {
4138
- id: "report",
4139
- label: "Report",
4140
- path: null
4141
- },
4142
4168
  {
4143
4169
  id: "values",
4144
4170
  label: "Values",
@@ -5067,10 +5093,10 @@ function renderFormHtml(form, tabs) {
5067
5093
  if (response.ok) {
5068
5094
  tabCache[tabId] = await response.text();
5069
5095
  } else {
5070
- tabCache[tabId] = '<div class="error">Failed to load content</div>';
5096
+ tabCache[tabId] = '<div class="error">No content available for this tab.</div>';
5071
5097
  }
5072
5098
  } catch (err) {
5073
- tabCache[tabId] = '<div class="error">Failed to load content</div>';
5099
+ tabCache[tabId] = '<div class="error">No content available for this tab.</div>';
5074
5100
  }
5075
5101
  }
5076
5102
  tabViewContent.innerHTML = tabCache[tabId];
@@ -5087,10 +5113,10 @@ function renderFormHtml(form, tabs) {
5087
5113
  if (response.ok) {
5088
5114
  tabCache[tabId] = await response.text();
5089
5115
  } else {
5090
- tabCache[tabId] = '<div class="error">Failed to load content</div>';
5116
+ tabCache[tabId] = '<div class="error">No content available for this tab.</div>';
5091
5117
  }
5092
5118
  } catch (err) {
5093
- tabCache[tabId] = '<div class="error">Failed to load content</div>';
5119
+ tabCache[tabId] = '<div class="error">No content available for this tab.</div>';
5094
5120
  }
5095
5121
  }
5096
5122
  tabOtherContent.innerHTML = tabCache[tabId];
@@ -5098,20 +5124,34 @@ function renderFormHtml(form, tabs) {
5098
5124
  }
5099
5125
  }
5100
5126
 
5127
+ // Navigate to a tab by id, updating the button state and hash
5128
+ let navigating = false;
5129
+ async function navigateToTab(tabId) {
5130
+ navigating = true;
5131
+ tabButtons.forEach(b => b.classList.remove('active'));
5132
+ const btn = document.querySelector('[data-tab="' + tabId + '"]');
5133
+ if (btn) btn.classList.add('active');
5134
+ location.hash = tabId;
5135
+ await showTab(tabId);
5136
+ navigating = false;
5137
+ }
5138
+
5101
5139
  tabButtons.forEach(btn => {
5102
5140
  btn.addEventListener('click', async () => {
5103
- const tabId = btn.dataset.tab;
5104
-
5105
- // Update active button
5106
- tabButtons.forEach(b => b.classList.remove('active'));
5107
- btn.classList.add('active');
5108
-
5109
- await showTab(tabId);
5141
+ await navigateToTab(btn.dataset.tab);
5110
5142
  });
5111
5143
  });
5112
5144
 
5113
- // Load View tab on page load (it's the default tab)
5114
- showTab('view');
5145
+ // Handle hash-based navigation (back/forward, direct URL)
5146
+ window.addEventListener('hashchange', () => {
5147
+ if (navigating) return;
5148
+ const hash = window.location.hash.slice(1);
5149
+ if (hash) navigateToTab(hash);
5150
+ });
5151
+
5152
+ // Load initial tab from hash or default to Form (view) tab
5153
+ const initialTab = window.location.hash.slice(1) || 'view';
5154
+ navigateToTab(initialTab);
5115
5155
 
5116
5156
  // URL copy tooltip functionality - initialize once
5117
5157
  (function initUrlCopyTooltip() {
@@ -5870,9 +5910,9 @@ function registerResearchCommand(program) {
5870
5910
  console.log(` ${formPath} ${pc.dim("(filled markform source)")}`);
5871
5911
  console.log(` ${schemaPath} ${pc.dim("(JSON Schema)")}`);
5872
5912
  if (options.transcript && result.transcript) {
5873
- const { serializeSession } = await import("./session-D7C7IlEv.mjs");
5913
+ const { serializeSession } = await import("./session-DHyTMP67.mjs");
5874
5914
  const transcriptPath = outputPath.replace(/\.form\.md$/, ".session.yaml");
5875
- const { writeFile } = await import("./shared-DtorFV21.mjs");
5915
+ const { writeFile } = await import("./shared-BLh342F5.mjs");
5876
5916
  await writeFile(transcriptPath, serializeSession(result.transcript));
5877
5917
  logInfo(ctx, `Transcript: ${transcriptPath}`);
5878
5918
  }
@@ -6123,6 +6163,11 @@ function registerSetCommand(program) {
6123
6163
  for (const rp of applyResult.rejectedPatches) logError(` ${rp.message}`);
6124
6164
  process.exit(1);
6125
6165
  }
6166
+ if (!(Boolean(options.clear) || Boolean(options.skip) || Boolean(options.abort) || options.delete !== void 0)) {
6167
+ const patchedFieldIds = new Set(patches.map((p) => "fieldId" in p ? p.fieldId : "").filter(Boolean));
6168
+ const relevantIssues = applyResult.issues.filter((i) => i.reason === "validation_error" && patchedFieldIds.has(i.ref));
6169
+ for (const issue of relevantIssues) logWarn(ctx, issue.message);
6170
+ }
6126
6171
  if (options.report) {
6127
6172
  const output = formatOutput(ctx, {
6128
6173
  apply_status: applyResult.applyStatus,
@@ -6631,4 +6676,4 @@ async function runCli() {
6631
6676
 
6632
6677
  //#endregion
6633
6678
  export { runCli as t };
6634
- //# sourceMappingURL=cli-B1DhFYBS.mjs.map
6679
+ //# sourceMappingURL=cli-B1T8kMFt.mjs.map