@agent-native/core 0.41.0 → 0.42.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.
Files changed (109) hide show
  1. package/dist/action.d.ts +13 -1
  2. package/dist/action.d.ts.map +1 -1
  3. package/dist/action.js.map +1 -1
  4. package/dist/agent/production-agent.d.ts +8 -0
  5. package/dist/agent/production-agent.d.ts.map +1 -1
  6. package/dist/agent/production-agent.js +93 -0
  7. package/dist/agent/production-agent.js.map +1 -1
  8. package/dist/cli/app-skill.d.ts +16 -0
  9. package/dist/cli/app-skill.d.ts.map +1 -1
  10. package/dist/cli/app-skill.js +33 -3
  11. package/dist/cli/app-skill.js.map +1 -1
  12. package/dist/cli/create.d.ts.map +1 -1
  13. package/dist/cli/create.js +57 -0
  14. package/dist/cli/create.js.map +1 -1
  15. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  16. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  17. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  18. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  19. package/dist/cli/recap.d.ts.map +1 -1
  20. package/dist/cli/recap.js +14 -3
  21. package/dist/cli/recap.js.map +1 -1
  22. package/dist/cli/skills.d.ts +34 -3
  23. package/dist/cli/skills.d.ts.map +1 -1
  24. package/dist/cli/skills.js +172 -48
  25. package/dist/cli/skills.js.map +1 -1
  26. package/dist/cli/workspacify.d.ts.map +1 -1
  27. package/dist/cli/workspacify.js +19 -4
  28. package/dist/cli/workspacify.js.map +1 -1
  29. package/dist/client/AssistantChat.d.ts.map +1 -1
  30. package/dist/client/AssistantChat.js +2 -2
  31. package/dist/client/AssistantChat.js.map +1 -1
  32. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  33. package/dist/client/agent-chat-adapter.js +172 -5
  34. package/dist/client/agent-chat-adapter.js.map +1 -1
  35. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts +19 -0
  36. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  37. package/dist/client/blocks/library/AnnotatedCodeBlock.js +5 -57
  38. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  39. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  40. package/dist/client/blocks/library/ApiEndpointBlock.js +116 -7
  41. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  42. package/dist/client/blocks/library/DataModelBlock.d.ts.map +1 -1
  43. package/dist/client/blocks/library/DataModelBlock.js +75 -9
  44. package/dist/client/blocks/library/DataModelBlock.js.map +1 -1
  45. package/dist/client/blocks/library/DiffBlock.d.ts +1 -1
  46. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  47. package/dist/client/blocks/library/DiffBlock.js +195 -34
  48. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  49. package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
  50. package/dist/client/blocks/library/HighlightedCode.js +1 -1
  51. package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
  52. package/dist/client/blocks/library/annotation-rail.d.ts +96 -0
  53. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -0
  54. package/dist/client/blocks/library/annotation-rail.js +120 -0
  55. package/dist/client/blocks/library/annotation-rail.js.map +1 -0
  56. package/dist/client/blocks/library/api-endpoint.config.d.ts +31 -6
  57. package/dist/client/blocks/library/api-endpoint.config.d.ts.map +1 -1
  58. package/dist/client/blocks/library/api-endpoint.config.js +30 -6
  59. package/dist/client/blocks/library/api-endpoint.config.js.map +1 -1
  60. package/dist/client/blocks/library/code.d.ts.map +1 -1
  61. package/dist/client/blocks/library/code.js +32 -15
  62. package/dist/client/blocks/library/code.js.map +1 -1
  63. package/dist/client/blocks/library/columns.d.ts.map +1 -1
  64. package/dist/client/blocks/library/columns.js +56 -35
  65. package/dist/client/blocks/library/columns.js.map +1 -1
  66. package/dist/client/blocks/library/data-model.config.d.ts +17 -0
  67. package/dist/client/blocks/library/data-model.config.d.ts.map +1 -1
  68. package/dist/client/blocks/library/data-model.config.js +15 -0
  69. package/dist/client/blocks/library/data-model.config.js.map +1 -1
  70. package/dist/client/blocks/library/diff.config.d.ts +28 -6
  71. package/dist/client/blocks/library/diff.config.d.ts.map +1 -1
  72. package/dist/client/blocks/library/diff.config.js +30 -6
  73. package/dist/client/blocks/library/diff.config.js.map +1 -1
  74. package/dist/client/blocks/types.d.ts +2 -2
  75. package/dist/client/blocks/types.d.ts.map +1 -1
  76. package/dist/client/blocks/types.js.map +1 -1
  77. package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
  78. package/dist/client/rich-markdown-editor/DragHandle.js +75 -9
  79. package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
  80. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts +25 -1
  81. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
  82. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +29 -6
  83. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  84. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +8 -1
  85. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  86. package/dist/client/rich-markdown-editor/SharedRichEditor.js +5 -1
  87. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  88. package/dist/extensions/actions.d.ts.map +1 -1
  89. package/dist/extensions/actions.js +159 -12
  90. package/dist/extensions/actions.js.map +1 -1
  91. package/dist/extensions/store.d.ts +21 -0
  92. package/dist/extensions/store.d.ts.map +1 -1
  93. package/dist/extensions/store.js +33 -1
  94. package/dist/extensions/store.js.map +1 -1
  95. package/dist/server/recap-image-route.d.ts.map +1 -1
  96. package/dist/server/recap-image-route.js +12 -3
  97. package/dist/server/recap-image-route.js.map +1 -1
  98. package/dist/templates/default/pnpm-workspace.yaml +7 -0
  99. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +30 -5
  100. package/dist/templates/workspace-root/package.json +0 -5
  101. package/dist/templates/workspace-root/pnpm-workspace.yaml +14 -0
  102. package/docs/content/plan-plugin.md +107 -0
  103. package/docs/content/skills-guide.md +8 -0
  104. package/docs/content/visual-plans.md +2 -0
  105. package/package.json +5 -1
  106. package/src/templates/default/pnpm-workspace.yaml +7 -0
  107. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +30 -5
  108. package/src/templates/workspace-root/package.json +0 -5
  109. package/src/templates/workspace-root/pnpm-workspace.yaml +14 -0
@@ -1 +1 @@
1
- {"version":3,"file":"DataModelBlock.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/DataModelBlock.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useRef, useState } from \"react\";\nimport {\n IconArrowNarrowRight,\n IconChevronRight,\n IconDatabase,\n IconKey,\n IconLink,\n IconPlus,\n IconTrash,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../../utils.js\";\nimport type { BlockEditProps, BlockReadProps } from \"../types.js\";\nimport type {\n DataModelData,\n DataModelEntity,\n DataModelField,\n DataModelRelation,\n DataModelRelationKind,\n} from \"./data-model.config.js\";\nimport { DevInput } from \"./dev-doc-ui.js\";\n\n/**\n * Read + Edit renderers for a `data-model` block — a dbdiagram / Prisma-style\n * entity-relationship diagram. Lives in core so any app can register the dev-doc\n * block (no shadcn import).\n */\n\n/* ── Resolution helpers (shared by Read + relation inference) ──────────────── */\n\n/** Split a `fk` string like `\"User.id\"` into `{ entity: \"User\", field: \"id\" }`. */\nfunction parseFk(fk: string): { entity: string; field?: string } {\n const trimmed = fk.trim();\n const dot = trimmed.indexOf(\".\");\n if (dot === -1) return { entity: trimmed };\n return {\n entity: trimmed.slice(0, dot).trim(),\n field: trimmed.slice(dot + 1).trim() || undefined,\n };\n}\n\n/**\n * Resolve an entity reference (used by `fk` targets and `relation.from`/`to`)\n * against the entity list by `id` first, then by case-insensitive `name`. Returns\n * the matched entity or `undefined`.\n */\nfunction resolveEntity(\n entities: DataModelEntity[],\n ref: string,\n): DataModelEntity | undefined {\n const needle = ref.trim();\n return (\n entities.find((entity) => entity.id === needle) ??\n entities.find(\n (entity) => entity.name.toLowerCase() === needle.toLowerCase(),\n )\n );\n}\n\n/** A short, readable label for an entity reference (its name, or the raw ref). */\nfunction entityLabel(entities: DataModelEntity[], ref: string): string {\n return resolveEntity(entities, ref)?.name ?? ref;\n}\n\n/** The cardinality glyph shown in the relations list (1:1 / 1:n / n:n). */\nfunction relationGlyph(kind?: DataModelRelationKind): string {\n if (kind === \"1-1\") return \"1:1\";\n if (kind === \"n-n\") return \"n:n\";\n return \"1:n\";\n}\n\n/**\n * Relations to render: explicit `relations` when present, otherwise inferred —\n * every `fk` field becomes a `1-n` relation from the referenced (parent) entity\n * to the entity holding the foreign key, so the connectors list is never empty\n * when the schema clearly implies them.\n */\nfunction effectiveRelations(data: DataModelData): DataModelRelation[] {\n if (data.relations && data.relations.length > 0) return data.relations;\n const inferred: DataModelRelation[] = [];\n for (const entity of data.entities) {\n for (const field of entity.fields) {\n if (!field.fk) continue;\n const target = resolveEntity(data.entities, parseFk(field.fk).entity);\n if (!target) continue;\n inferred.push({\n from: target.id,\n to: entity.id,\n kind: \"1-n\",\n label: field.name,\n });\n }\n }\n return inferred;\n}\n\n/* ── Read (interactive ERD) ────────────────────────────────────────────────── */\n\n/**\n * Read-only renderer for a `data-model` block — a dbdiagram / Prisma-style\n * entity-relationship diagram. Each entity is a collapsible card: the header\n * shows the entity name + field count, and expanding it reveals a compact field\n * table (Field · Type · flags) with PK / FK / nullable indicators.\n *\n * INTERACTIVITY (the reason this is a custom block, not a plain table): hovering\n * or clicking a foreign-key field highlights the referenced entity card — it\n * scrolls into view, expands, and gets a temporary accent ring — so a reader can\n * trace a relationship across the whole model. Explicit `relations` (or relations\n * inferred from `fk` fields) render as a labeled connector list below the cards.\n *\n * Every color is theme-aware via Tailwind `dark:` variants or plan CSS vars, so\n * the diagram reads correctly in both the `.dark` plan theme and light mode.\n */\nexport function DataModelRead({\n data,\n blockId,\n title,\n summary,\n}: BlockReadProps<DataModelData>) {\n const entities = data.entities ?? [];\n const relations = useMemo(() => effectiveRelations(data), [data]);\n\n // Per-entity collapse state. Default: the first entity expanded (or all of them\n // when the model is small) so the block is useful at a glance without a click.\n const [expanded, setExpanded] = useState<Record<string, boolean>>(() => {\n const initial: Record<string, boolean> = {};\n const expandAll = entities.length <= 2;\n entities.forEach((entity, index) => {\n initial[entity.id] = expandAll || index === 0;\n });\n return initial;\n });\n\n // Which entity is being hovered/clicked-to via an FK — drives the accent ring.\n const [highlighted, setHighlighted] = useState<string | null>(null);\n const cardRefs = useRef<Record<string, HTMLDivElement | null>>({});\n\n const toggle = useCallback((id: string) => {\n setExpanded((current) => ({ ...current, [id]: !current[id] }));\n }, []);\n\n // Highlight + reveal a referenced entity: expand it, ring it, and scroll it\n // into view. Used on FK hover (transient) and click (scroll).\n const focusEntity = useCallback(\n (targetId: string | undefined, scroll: boolean) => {\n if (!targetId) {\n setHighlighted(null);\n return;\n }\n setHighlighted(targetId);\n if (scroll) {\n setExpanded((current) => ({ ...current, [targetId]: true }));\n cardRefs.current[targetId]?.scrollIntoView({\n behavior: \"smooth\",\n block: \"nearest\",\n });\n }\n },\n [],\n );\n\n return (\n <section className=\"plan-block\" data-block-id={blockId}>\n {title && <div className=\"plan-block-label\">{title}</div>}\n\n <div className=\"flex flex-col gap-3\">\n {entities.map((entity) => {\n const isOpen = expanded[entity.id] ?? false;\n const isHighlighted = highlighted === entity.id;\n return (\n <div\n key={entity.id}\n ref={(node) => {\n cardRefs.current[entity.id] = node;\n }}\n data-entity-id={entity.id}\n className={cn(\n \"overflow-hidden rounded-xl border bg-plan-block transition-shadow\",\n isHighlighted\n ? \"border-blue-400 ring-2 ring-blue-400/60 dark:border-blue-400 dark:ring-blue-400/50\"\n : \"border-plan-line\",\n )}\n >\n {/* Entity header — always visible, toggles the field table. */}\n <button\n type=\"button\"\n data-plan-interactive\n aria-expanded={isOpen}\n onClick={() => toggle(entity.id)}\n className=\"flex w-full items-center gap-2 px-4 py-2.5 text-left transition-colors hover:bg-accent/40\"\n >\n <IconChevronRight\n className={cn(\n \"size-4 shrink-0 text-plan-muted transition-transform\",\n isOpen && \"rotate-90\",\n )}\n />\n <IconDatabase className=\"size-4 shrink-0 text-blue-600 dark:text-blue-300\" />\n <span className=\"min-w-0 truncate font-mono text-sm font-semibold text-plan-text\">\n {entity.name}\n </span>\n <span className=\"ml-auto shrink-0 rounded-full bg-accent/50 px-2 py-0.5 text-[11px] font-medium text-plan-muted\">\n {entity.fields.length}{\" \"}\n {entity.fields.length === 1 ? \"field\" : \"fields\"}\n </span>\n </button>\n\n {/* Expanded field table. */}\n {isOpen && (\n <div className=\"border-t border-plan-line\">\n {entity.note && (\n <p className=\"px-4 pt-2 text-xs italic text-plan-muted\">\n {entity.note}\n </p>\n )}\n <table className=\"w-full border-collapse text-sm\">\n <tbody>\n {entity.fields.map((field, index) => {\n const fkTarget = field.fk\n ? resolveEntity(entities, parseFk(field.fk).entity)\n : undefined;\n return (\n <tr\n key={`${field.name}-${index}`}\n className={cn(\n \"border-t border-plan-line/70 align-top first:border-t-0\",\n field.fk && \"cursor-pointer hover:bg-blue-500/5\",\n )}\n // FK interactivity: hovering rings the referenced\n // entity card; clicking also scrolls it into view.\n onMouseEnter={\n fkTarget\n ? () => focusEntity(fkTarget.id, false)\n : undefined\n }\n onMouseLeave={\n fkTarget\n ? () => focusEntity(undefined, false)\n : undefined\n }\n onClick={\n fkTarget\n ? () => focusEntity(fkTarget.id, true)\n : undefined\n }\n >\n <td className=\"w-px whitespace-nowrap py-1.5 pl-4 pr-2\">\n <div className=\"flex items-center gap-1.5\">\n {field.pk && (\n <IconKey\n className=\"size-3.5 shrink-0 text-amber-500 dark:text-amber-300\"\n aria-label=\"Primary key\"\n />\n )}\n {field.fk && (\n <IconLink\n className=\"size-3.5 shrink-0 text-blue-500 dark:text-blue-300\"\n aria-label=\"Foreign key\"\n />\n )}\n <span\n className={cn(\n \"font-mono text-xs\",\n field.pk\n ? \"font-semibold text-plan-text\"\n : \"text-plan-text\",\n )}\n >\n {field.name}\n </span>\n </div>\n </td>\n <td className=\"py-1.5 pr-2\">\n {field.type && (\n <span className=\"inline-block rounded bg-accent/40 px-1.5 py-0.5 font-mono text-[11px] text-plan-muted\">\n {field.type}\n </span>\n )}\n </td>\n <td className=\"py-1.5 pr-4 text-right\">\n <div className=\"flex flex-wrap items-center justify-end gap-1\">\n {field.pk && (\n <span className=\"rounded px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-amber-600 dark:text-amber-300 bg-amber-100 dark:bg-amber-500/15\">\n PK\n </span>\n )}\n {field.fk && (\n <span className=\"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-semibold text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-500/15\">\n FK\n <span className=\"font-mono font-normal opacity-90\">\n {fkTarget\n ? `${fkTarget.name}${\n parseFk(field.fk).field\n ? `.${parseFk(field.fk).field}`\n : \"\"\n }`\n : field.fk}\n </span>\n </span>\n )}\n {field.nullable && (\n <span className=\"rounded px-1.5 py-0.5 text-[10px] font-medium text-plan-muted bg-accent/50\">\n nullable\n </span>\n )}\n {field.default != null &&\n field.default !== \"\" && (\n <span className=\"rounded px-1.5 py-0.5 font-mono text-[10px] text-plan-muted bg-accent/40\">\n = {field.default}\n </span>\n )}\n </div>\n {field.note && (\n <div className=\"mt-0.5 text-[11px] italic text-plan-muted\">\n {field.note}\n </div>\n )}\n </td>\n </tr>\n );\n })}\n {entity.fields.length === 0 && (\n <tr>\n <td className=\"px-4 py-2 text-xs text-plan-muted\">\n No fields yet.\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n )}\n </div>\n );\n })}\n </div>\n\n {/* Relations / connectors list. */}\n {relations.length > 0 && (\n <div className=\"mt-4\">\n <div className=\"text-xs font-semibold uppercase tracking-wide text-plan-muted\">\n Relations\n </div>\n <div className=\"mt-2 flex flex-col gap-1.5\">\n {relations.map((relation, index) => {\n const fromEntity = resolveEntity(entities, relation.from);\n const toEntity = resolveEntity(entities, relation.to);\n return (\n <button\n key={`${relation.from}-${relation.to}-${index}`}\n type=\"button\"\n data-plan-interactive\n className=\"group flex w-fit items-center gap-2 rounded-md px-2 py-1 text-sm transition-colors hover:bg-accent/40\"\n onMouseEnter={() => focusEntity(toEntity?.id, false)}\n onMouseLeave={() => focusEntity(undefined, false)}\n onClick={() => focusEntity(toEntity?.id, true)}\n >\n <span className=\"font-mono text-xs font-semibold text-plan-text\">\n {entityLabel(entities, relation.from)}\n </span>\n <span className=\"flex items-center gap-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-[10px] font-bold text-blue-700 dark:bg-blue-500/15 dark:text-blue-300\">\n {relationGlyph(relation.kind)}\n <IconArrowNarrowRight className=\"size-3\" />\n </span>\n <span className=\"font-mono text-xs font-semibold text-plan-text\">\n {entityLabel(entities, relation.to)}\n </span>\n {relation.label && (\n <span className=\"text-xs text-plan-muted\">\n · {relation.label}\n </span>\n )}\n {!fromEntity || !toEntity ? (\n <span className=\"text-[10px] text-amber-600 dark:text-amber-300\">\n (unresolved)\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n </div>\n )}\n\n {summary && <p className=\"mt-5 text-plan-muted\">{summary}</p>}\n </section>\n );\n}\n\n/* ── Edit (panel form) ─────────────────────────────────────────────────────── */\n\nlet entitySeq = 0;\n/** Stable-enough new entity id for a freshly-added entity in the editor. */\nfunction newEntityId(): string {\n entitySeq += 1;\n return `e_${Date.now().toString(36)}_${entitySeq}`;\n}\n\n/**\n * Panel editor for a `data-model` block. A structured form: a list of entities\n * (add/remove), each with a name Input, an optional note, and repeatable field\n * rows (add/remove) carrying name / type / PK checkbox / FK input / nullable\n * checkbox. Relations are derived from `fk` in v1, so the form focuses on the\n * entities + fields. Renders BARE content (no `<section>`); the registry's panel\n * surface supplies the popover chrome.\n */\nexport function DataModelEdit({\n data,\n onChange,\n editable,\n}: BlockEditProps<DataModelData>) {\n const entities = data.entities ?? [];\n\n const patchEntities = (next: DataModelEntity[]) =>\n onChange({ ...data, entities: next });\n\n const updateEntity = (index: number, next: Partial<DataModelEntity>) =>\n patchEntities(\n entities.map((entity, i) =>\n i === index ? { ...entity, ...next } : entity,\n ),\n );\n\n const removeEntity = (index: number) =>\n patchEntities(entities.filter((_, i) => i !== index));\n\n const addEntity = () =>\n patchEntities([\n ...entities,\n {\n id: newEntityId(),\n name: \"NewEntity\",\n fields: [{ name: \"id\", pk: true }],\n },\n ]);\n\n const updateField = (\n entityIndex: number,\n fieldIndex: number,\n next: Partial<DataModelField>,\n ) => {\n const entity = entities[entityIndex];\n if (!entity) return;\n updateEntity(entityIndex, {\n fields: entity.fields.map((field, i) =>\n i === fieldIndex ? { ...field, ...next } : field,\n ),\n });\n };\n\n const removeField = (entityIndex: number, fieldIndex: number) => {\n const entity = entities[entityIndex];\n if (!entity) return;\n updateEntity(entityIndex, {\n fields: entity.fields.filter((_, i) => i !== fieldIndex),\n });\n };\n\n const addField = (entityIndex: number) => {\n const entity = entities[entityIndex];\n if (!entity) return;\n updateEntity(entityIndex, {\n fields: [...entity.fields, { name: \"field\" }],\n });\n };\n\n return (\n <div className=\"flex flex-col gap-4\" data-plan-interactive>\n {entities.map((entity, entityIndex) => (\n <div\n key={entity.id}\n className=\"flex flex-col gap-2 rounded-lg border border-input p-3\"\n >\n <div className=\"flex items-center gap-2\">\n <IconDatabase className=\"size-4 shrink-0 text-muted-foreground\" />\n <DevInput\n className=\"h-8 font-mono text-sm font-semibold\"\n value={entity.name}\n disabled={!editable}\n placeholder=\"EntityName\"\n onChange={(event) =>\n updateEntity(entityIndex, { name: event.target.value })\n }\n />\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n aria-label=\"Remove entity\"\n className=\"flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground\"\n onClick={() => removeEntity(entityIndex)}\n >\n <IconTrash className=\"size-4\" />\n </button>\n )}\n </div>\n\n {/* Field rows. */}\n <div className=\"flex flex-col gap-1.5\">\n {entity.fields.map((field, fieldIndex) => (\n <div\n key={fieldIndex}\n className=\"flex flex-col gap-1.5 rounded-md border border-input/60 bg-accent/20 p-2\"\n >\n <div className=\"grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto] gap-1.5\">\n <DevInput\n className=\"h-7 font-mono text-xs\"\n value={field.name}\n disabled={!editable}\n placeholder=\"name\"\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n name: event.target.value,\n })\n }\n />\n <DevInput\n className=\"h-7 font-mono text-xs\"\n value={field.type ?? \"\"}\n disabled={!editable}\n placeholder=\"type (e.g. uuid)\"\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n type: event.target.value || undefined,\n })\n }\n />\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n aria-label=\"Remove field\"\n className=\"flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground\"\n onClick={() => removeField(entityIndex, fieldIndex)}\n >\n <IconTrash className=\"size-3.5\" />\n </button>\n )}\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-1.5 whitespace-nowrap text-xs text-muted-foreground\">\n <input\n type=\"checkbox\"\n className=\"size-3.5 cursor-pointer accent-primary\"\n checked={Boolean(field.pk)}\n disabled={!editable}\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n pk: event.target.checked || undefined,\n })\n }\n />\n PK\n </label>\n <label className=\"flex items-center gap-1.5 whitespace-nowrap text-xs text-muted-foreground\">\n <input\n type=\"checkbox\"\n className=\"size-3.5 cursor-pointer accent-primary\"\n checked={Boolean(field.nullable)}\n disabled={!editable}\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n nullable: event.target.checked || undefined,\n })\n }\n />\n Nullable\n </label>\n <DevInput\n className=\"h-7 flex-1 font-mono text-xs\"\n value={field.fk ?? \"\"}\n disabled={!editable}\n placeholder=\"FK → Entity.field\"\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n fk: event.target.value || undefined,\n })\n }\n />\n </div>\n </div>\n ))}\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n className=\"flex w-fit items-center gap-1 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent/60 hover:text-foreground\"\n onClick={() => addField(entityIndex)}\n >\n <IconPlus className=\"size-3.5\" />\n Add field\n </button>\n )}\n </div>\n </div>\n ))}\n\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n className=\"flex items-center justify-center gap-1.5 rounded-md border border-dashed border-input py-2 text-sm text-muted-foreground hover:bg-accent/40 hover:text-foreground\"\n onClick={addEntity}\n >\n <IconPlus className=\"size-4\" />\n Add entity\n </button>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"DataModelBlock.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/DataModelBlock.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useRef, useState } from \"react\";\nimport {\n IconArrowNarrowRight,\n IconChevronRight,\n IconDatabase,\n IconKey,\n IconLink,\n IconPlus,\n IconTrash,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../../utils.js\";\nimport type { BlockEditProps, BlockReadProps } from \"../types.js\";\nimport type {\n DataModelChange,\n DataModelData,\n DataModelEntity,\n DataModelField,\n DataModelRelation,\n DataModelRelationKind,\n} from \"./data-model.config.js\";\nimport { DATA_MODEL_CHANGES } from \"./data-model.config.js\";\nimport { DevInput, DevSelect } from \"./dev-doc-ui.js\";\n\n/**\n * Read + Edit renderers for a `data-model` block — a dbdiagram / Prisma-style\n * entity-relationship diagram. Lives in core so any app can register the dev-doc\n * block (no shadcn import).\n */\n\n/* ── Theme-aware change tokens (shared vocabulary with `file-tree`) ─────────── */\n\n/**\n * Change-chip palette — the SAME tinted-bg + saturated-text scheme the\n * `file-tree` block uses, in BOTH the `.dark` plan theme and light mode (never a\n * dark-only palette). Keeps data-model diff chips visually consistent with the\n * file-tree change badges so a reviewer reads one vocabulary across dev-doc\n * blocks.\n */\nconst CHANGE_BADGE: Record<DataModelChange, string> = {\n added:\n \"bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300\",\n modified: \"bg-blue-100 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300\",\n removed: \"bg-red-100 text-red-700 dark:bg-red-500/15 dark:text-red-300\",\n renamed:\n \"bg-violet-100 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300\",\n};\n\n/** Human-readable chip label, matching the file-tree change labels. */\nconst CHANGE_LABEL: Record<DataModelChange, string> = {\n added: \"Added\",\n modified: \"Modified\",\n removed: \"Removed\",\n renamed: \"Renamed\",\n};\n\n/** Accent ink for a changed field/entity name, echoing its change color. */\nconst CHANGE_NAME_INK: Record<DataModelChange, string> = {\n added: \"text-emerald-700 dark:text-emerald-300\",\n modified: \"text-blue-700 dark:text-blue-300\",\n removed: \"text-red-600 line-through dark:text-red-300\",\n renamed: \"text-violet-700 dark:text-violet-300\",\n};\n\n/** Subtle left accent rule on a changed field row (added = green, removed = red). */\nconst CHANGE_ROW_ACCENT: Record<DataModelChange, string> = {\n added: \"border-l-2 border-l-emerald-400 dark:border-l-emerald-500/60\",\n modified: \"border-l-2 border-l-blue-400 dark:border-l-blue-500/60\",\n removed: \"border-l-2 border-l-red-400 dark:border-l-red-500/60\",\n renamed: \"border-l-2 border-l-violet-400 dark:border-l-violet-500/60\",\n};\n\n/** A small theme-aware change chip (\"Added\" / \"Modified\" / …). */\nfunction ChangeChip({\n change,\n className,\n}: {\n change: DataModelChange;\n className?: string;\n}) {\n return (\n <span\n title={CHANGE_LABEL[change]}\n aria-label={CHANGE_LABEL[change]}\n className={cn(\n \"rounded px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide leading-none\",\n CHANGE_BADGE[change],\n className,\n )}\n >\n {CHANGE_LABEL[change]}\n </span>\n );\n}\n\n/* ── Resolution helpers (shared by Read + relation inference) ──────────────── */\n\n/** Split a `fk` string like `\"User.id\"` into `{ entity: \"User\", field: \"id\" }`. */\nfunction parseFk(fk: string): { entity: string; field?: string } {\n const trimmed = fk.trim();\n const dot = trimmed.indexOf(\".\");\n if (dot === -1) return { entity: trimmed };\n return {\n entity: trimmed.slice(0, dot).trim(),\n field: trimmed.slice(dot + 1).trim() || undefined,\n };\n}\n\n/**\n * Resolve an entity reference (used by `fk` targets and `relation.from`/`to`)\n * against the entity list by `id` first, then by case-insensitive `name`. Returns\n * the matched entity or `undefined`.\n */\nfunction resolveEntity(\n entities: DataModelEntity[],\n ref: string,\n): DataModelEntity | undefined {\n const needle = ref.trim();\n return (\n entities.find((entity) => entity.id === needle) ??\n entities.find(\n (entity) => entity.name.toLowerCase() === needle.toLowerCase(),\n )\n );\n}\n\n/** A short, readable label for an entity reference (its name, or the raw ref). */\nfunction entityLabel(entities: DataModelEntity[], ref: string): string {\n return resolveEntity(entities, ref)?.name ?? ref;\n}\n\n/** The cardinality glyph shown in the relations list (1:1 / 1:n / n:n). */\nfunction relationGlyph(kind?: DataModelRelationKind): string {\n if (kind === \"1-1\") return \"1:1\";\n if (kind === \"n-n\") return \"n:n\";\n return \"1:n\";\n}\n\n/**\n * Relations to render: explicit `relations` when present, otherwise inferred —\n * every `fk` field becomes a `1-n` relation from the referenced (parent) entity\n * to the entity holding the foreign key, so the connectors list is never empty\n * when the schema clearly implies them.\n */\nfunction effectiveRelations(data: DataModelData): DataModelRelation[] {\n if (data.relations && data.relations.length > 0) return data.relations;\n const inferred: DataModelRelation[] = [];\n for (const entity of data.entities) {\n for (const field of entity.fields) {\n if (!field.fk) continue;\n const target = resolveEntity(data.entities, parseFk(field.fk).entity);\n if (!target) continue;\n inferred.push({\n from: target.id,\n to: entity.id,\n kind: \"1-n\",\n label: field.name,\n });\n }\n }\n return inferred;\n}\n\n/* ── Read (interactive ERD) ────────────────────────────────────────────────── */\n\n/**\n * Read-only renderer for a `data-model` block — a dbdiagram / Prisma-style\n * entity-relationship diagram. Each entity is a collapsible card: the header\n * shows the entity name + field count, and expanding it reveals a compact field\n * table (Field · Type · flags) with PK / FK / nullable indicators.\n *\n * INTERACTIVITY (the reason this is a custom block, not a plain table): hovering\n * or clicking a foreign-key field highlights the referenced entity card — it\n * scrolls into view, expands, and gets a temporary accent ring — so a reader can\n * trace a relationship across the whole model. Explicit `relations` (or relations\n * inferred from `fk` fields) render as a labeled connector list below the cards.\n *\n * Every color is theme-aware via Tailwind `dark:` variants or plan CSS vars, so\n * the diagram reads correctly in both the `.dark` plan theme and light mode.\n */\nexport function DataModelRead({\n data,\n blockId,\n title,\n summary,\n}: BlockReadProps<DataModelData>) {\n const entities = data.entities ?? [];\n const relations = useMemo(() => effectiveRelations(data), [data]);\n\n // Per-entity collapse state. Default: the first entity expanded (or all of them\n // when the model is small) so the block is useful at a glance without a click.\n const [expanded, setExpanded] = useState<Record<string, boolean>>(() => {\n const initial: Record<string, boolean> = {};\n const expandAll = entities.length <= 2;\n entities.forEach((entity, index) => {\n initial[entity.id] = expandAll || index === 0;\n });\n return initial;\n });\n\n // Which entity is being hovered/clicked-to via an FK — drives the accent ring.\n const [highlighted, setHighlighted] = useState<string | null>(null);\n const cardRefs = useRef<Record<string, HTMLDivElement | null>>({});\n\n const toggle = useCallback((id: string) => {\n setExpanded((current) => ({ ...current, [id]: !current[id] }));\n }, []);\n\n // Highlight + reveal a referenced entity: expand it, ring it, and scroll it\n // into view. Used on FK hover (transient) and click (scroll).\n const focusEntity = useCallback(\n (targetId: string | undefined, scroll: boolean) => {\n if (!targetId) {\n setHighlighted(null);\n return;\n }\n setHighlighted(targetId);\n if (scroll) {\n setExpanded((current) => ({ ...current, [targetId]: true }));\n cardRefs.current[targetId]?.scrollIntoView({\n behavior: \"smooth\",\n block: \"nearest\",\n });\n }\n },\n [],\n );\n\n return (\n <section className=\"plan-block\" data-block-id={blockId}>\n {title && <div className=\"plan-block-label\">{title}</div>}\n\n <div className=\"flex flex-col gap-3\">\n {entities.map((entity) => {\n const isOpen = expanded[entity.id] ?? false;\n const isHighlighted = highlighted === entity.id;\n return (\n <div\n key={entity.id}\n ref={(node) => {\n cardRefs.current[entity.id] = node;\n }}\n data-entity-id={entity.id}\n className={cn(\n \"overflow-hidden rounded-xl border bg-plan-block transition-shadow\",\n isHighlighted\n ? \"border-blue-400 ring-2 ring-blue-400/60 dark:border-blue-400 dark:ring-blue-400/50\"\n : \"border-plan-line\",\n )}\n >\n {/* Entity header — always visible, toggles the field table. */}\n <button\n type=\"button\"\n data-plan-interactive\n aria-expanded={isOpen}\n onClick={() => toggle(entity.id)}\n className=\"flex w-full items-center gap-2 px-4 py-2.5 text-left transition-colors hover:bg-accent/40\"\n >\n <IconChevronRight\n className={cn(\n \"size-4 shrink-0 text-plan-muted transition-transform\",\n isOpen && \"rotate-90\",\n )}\n />\n <IconDatabase className=\"size-4 shrink-0 text-blue-600 dark:text-blue-300\" />\n <span\n className={cn(\n \"min-w-0 truncate font-mono text-sm font-semibold\",\n entity.change\n ? CHANGE_NAME_INK[entity.change]\n : \"text-plan-text\",\n )}\n >\n {entity.name}\n </span>\n {entity.change && <ChangeChip change={entity.change} />}\n <span className=\"ml-auto shrink-0 rounded-full bg-accent/50 px-2 py-0.5 text-[11px] font-medium text-plan-muted\">\n {entity.fields.length}{\" \"}\n {entity.fields.length === 1 ? \"field\" : \"fields\"}\n </span>\n </button>\n\n {/* Expanded field table. */}\n {isOpen && (\n <div className=\"border-t border-plan-line\">\n {entity.note && (\n <p className=\"px-4 pt-2 text-xs italic text-plan-muted\">\n {entity.note}\n </p>\n )}\n <table className=\"w-full border-collapse text-sm\">\n <tbody>\n {entity.fields.map((field, index) => {\n const fkTarget = field.fk\n ? resolveEntity(entities, parseFk(field.fk).entity)\n : undefined;\n return (\n <tr\n key={`${field.name}-${index}`}\n className={cn(\n \"border-t border-plan-line/70 align-top first:border-t-0\",\n field.fk && \"cursor-pointer hover:bg-blue-500/5\",\n field.change && CHANGE_ROW_ACCENT[field.change],\n )}\n // FK interactivity: hovering rings the referenced\n // entity card; clicking also scrolls it into view.\n onMouseEnter={\n fkTarget\n ? () => focusEntity(fkTarget.id, false)\n : undefined\n }\n onMouseLeave={\n fkTarget\n ? () => focusEntity(undefined, false)\n : undefined\n }\n onClick={\n fkTarget\n ? () => focusEntity(fkTarget.id, true)\n : undefined\n }\n >\n <td className=\"w-px whitespace-nowrap py-1.5 pl-4 pr-2\">\n <div className=\"flex items-center gap-1.5\">\n {field.pk && (\n <IconKey\n className=\"size-3.5 shrink-0 text-amber-500 dark:text-amber-300\"\n aria-label=\"Primary key\"\n />\n )}\n {field.fk && (\n <IconLink\n className=\"size-3.5 shrink-0 text-blue-500 dark:text-blue-300\"\n aria-label=\"Foreign key\"\n />\n )}\n <span\n className={cn(\n \"font-mono text-xs\",\n field.pk && \"font-semibold\",\n field.change\n ? CHANGE_NAME_INK[field.change]\n : \"text-plan-text\",\n )}\n >\n {field.name}\n </span>\n </div>\n </td>\n <td className=\"py-1.5 pr-2\">\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {/* Prior value (`was`) for a modified field —\n struck through ahead of the current type. */}\n {field.change === \"modified\" && field.was && (\n <>\n <span className=\"inline-block rounded bg-accent/30 px-1.5 py-0.5 font-mono text-[11px] text-plan-muted line-through\">\n {field.was}\n </span>\n <IconArrowNarrowRight\n className=\"size-3 shrink-0 text-plan-muted\"\n aria-hidden\n />\n </>\n )}\n {field.type && (\n <span\n className={cn(\n \"inline-block rounded bg-accent/40 px-1.5 py-0.5 font-mono text-[11px]\",\n field.change === \"removed\"\n ? \"text-plan-muted line-through\"\n : \"text-plan-muted\",\n )}\n >\n {field.type}\n </span>\n )}\n </div>\n </td>\n <td className=\"py-1.5 pr-4 text-right\">\n <div className=\"flex flex-wrap items-center justify-end gap-1\">\n {field.change && (\n <ChangeChip change={field.change} />\n )}\n {field.pk && (\n <span className=\"rounded px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-amber-600 dark:text-amber-300 bg-amber-100 dark:bg-amber-500/15\">\n PK\n </span>\n )}\n {field.fk && (\n <span className=\"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-semibold text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-500/15\">\n FK\n <span className=\"font-mono font-normal opacity-90\">\n {fkTarget\n ? `${fkTarget.name}${\n parseFk(field.fk).field\n ? `.${parseFk(field.fk).field}`\n : \"\"\n }`\n : field.fk}\n </span>\n </span>\n )}\n {field.nullable && (\n <span className=\"rounded px-1.5 py-0.5 text-[10px] font-medium text-plan-muted bg-accent/50\">\n nullable\n </span>\n )}\n {field.default != null &&\n field.default !== \"\" && (\n <span className=\"rounded px-1.5 py-0.5 font-mono text-[10px] text-plan-muted bg-accent/40\">\n = {field.default}\n </span>\n )}\n </div>\n {field.note && (\n <div className=\"mt-0.5 text-[11px] italic text-plan-muted\">\n {field.note}\n </div>\n )}\n </td>\n </tr>\n );\n })}\n {entity.fields.length === 0 && (\n <tr>\n <td className=\"px-4 py-2 text-xs text-plan-muted\">\n No fields yet.\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n )}\n </div>\n );\n })}\n </div>\n\n {/* Relations / connectors list. */}\n {relations.length > 0 && (\n <div className=\"mt-4\">\n <div className=\"text-xs font-semibold uppercase tracking-wide text-plan-muted\">\n Relations\n </div>\n <div className=\"mt-2 flex flex-col gap-1.5\">\n {relations.map((relation, index) => {\n const fromEntity = resolveEntity(entities, relation.from);\n const toEntity = resolveEntity(entities, relation.to);\n return (\n <button\n key={`${relation.from}-${relation.to}-${index}`}\n type=\"button\"\n data-plan-interactive\n className=\"group flex w-fit items-center gap-2 rounded-md px-2 py-1 text-sm transition-colors hover:bg-accent/40\"\n onMouseEnter={() => focusEntity(toEntity?.id, false)}\n onMouseLeave={() => focusEntity(undefined, false)}\n onClick={() => focusEntity(toEntity?.id, true)}\n >\n <span className=\"font-mono text-xs font-semibold text-plan-text\">\n {entityLabel(entities, relation.from)}\n </span>\n <span className=\"flex items-center gap-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-[10px] font-bold text-blue-700 dark:bg-blue-500/15 dark:text-blue-300\">\n {relationGlyph(relation.kind)}\n <IconArrowNarrowRight className=\"size-3\" />\n </span>\n <span className=\"font-mono text-xs font-semibold text-plan-text\">\n {entityLabel(entities, relation.to)}\n </span>\n {relation.label && (\n <span className=\"text-xs text-plan-muted\">\n · {relation.label}\n </span>\n )}\n {!fromEntity || !toEntity ? (\n <span className=\"text-[10px] text-amber-600 dark:text-amber-300\">\n (unresolved)\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n </div>\n )}\n\n {summary && <p className=\"mt-5 text-plan-muted\">{summary}</p>}\n </section>\n );\n}\n\n/* ── Edit (panel form) ─────────────────────────────────────────────────────── */\n\nlet entitySeq = 0;\n/** Stable-enough new entity id for a freshly-added entity in the editor. */\nfunction newEntityId(): string {\n entitySeq += 1;\n return `e_${Date.now().toString(36)}_${entitySeq}`;\n}\n\n/**\n * Panel editor for a `data-model` block. A structured form: a list of entities\n * (add/remove), each with a name Input, an optional note, and repeatable field\n * rows (add/remove) carrying name / type / PK checkbox / FK input / nullable\n * checkbox. Relations are derived from `fk` in v1, so the form focuses on the\n * entities + fields. Renders BARE content (no `<section>`); the registry's panel\n * surface supplies the popover chrome.\n */\nexport function DataModelEdit({\n data,\n onChange,\n editable,\n}: BlockEditProps<DataModelData>) {\n const entities = data.entities ?? [];\n\n const patchEntities = (next: DataModelEntity[]) =>\n onChange({ ...data, entities: next });\n\n const updateEntity = (index: number, next: Partial<DataModelEntity>) =>\n patchEntities(\n entities.map((entity, i) =>\n i === index ? { ...entity, ...next } : entity,\n ),\n );\n\n const removeEntity = (index: number) =>\n patchEntities(entities.filter((_, i) => i !== index));\n\n const addEntity = () =>\n patchEntities([\n ...entities,\n {\n id: newEntityId(),\n name: \"NewEntity\",\n fields: [{ name: \"id\", pk: true }],\n },\n ]);\n\n const updateField = (\n entityIndex: number,\n fieldIndex: number,\n next: Partial<DataModelField>,\n ) => {\n const entity = entities[entityIndex];\n if (!entity) return;\n updateEntity(entityIndex, {\n fields: entity.fields.map((field, i) =>\n i === fieldIndex ? { ...field, ...next } : field,\n ),\n });\n };\n\n const removeField = (entityIndex: number, fieldIndex: number) => {\n const entity = entities[entityIndex];\n if (!entity) return;\n updateEntity(entityIndex, {\n fields: entity.fields.filter((_, i) => i !== fieldIndex),\n });\n };\n\n const addField = (entityIndex: number) => {\n const entity = entities[entityIndex];\n if (!entity) return;\n updateEntity(entityIndex, {\n fields: [...entity.fields, { name: \"field\" }],\n });\n };\n\n return (\n <div className=\"flex flex-col gap-4\" data-plan-interactive>\n {entities.map((entity, entityIndex) => (\n <div\n key={entity.id}\n className=\"flex flex-col gap-2 rounded-lg border border-input p-3\"\n >\n <div className=\"flex items-center gap-2\">\n <IconDatabase className=\"size-4 shrink-0 text-muted-foreground\" />\n <DevInput\n className=\"h-8 font-mono text-sm font-semibold\"\n value={entity.name}\n disabled={!editable}\n placeholder=\"EntityName\"\n onChange={(event) =>\n updateEntity(entityIndex, { name: event.target.value })\n }\n />\n <DevSelect\n className=\"h-8 w-[120px] shrink-0\"\n value={entity.change ?? \"none\"}\n disabled={!editable}\n onValueChange={(value) =>\n updateEntity(entityIndex, {\n change:\n value === \"none\" ? undefined : (value as DataModelChange),\n })\n }\n options={[\n { value: \"none\", label: \"No change\" },\n ...DATA_MODEL_CHANGES.map((change) => ({\n value: change,\n label: CHANGE_LABEL[change],\n })),\n ]}\n />\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n aria-label=\"Remove entity\"\n className=\"flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground\"\n onClick={() => removeEntity(entityIndex)}\n >\n <IconTrash className=\"size-4\" />\n </button>\n )}\n </div>\n\n {/* Field rows. */}\n <div className=\"flex flex-col gap-1.5\">\n {entity.fields.map((field, fieldIndex) => (\n <div\n key={fieldIndex}\n className=\"flex flex-col gap-1.5 rounded-md border border-input/60 bg-accent/20 p-2\"\n >\n <div className=\"grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto] gap-1.5\">\n <DevInput\n className=\"h-7 font-mono text-xs\"\n value={field.name}\n disabled={!editable}\n placeholder=\"name\"\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n name: event.target.value,\n })\n }\n />\n <DevInput\n className=\"h-7 font-mono text-xs\"\n value={field.type ?? \"\"}\n disabled={!editable}\n placeholder=\"type (e.g. uuid)\"\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n type: event.target.value || undefined,\n })\n }\n />\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n aria-label=\"Remove field\"\n className=\"flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground\"\n onClick={() => removeField(entityIndex, fieldIndex)}\n >\n <IconTrash className=\"size-3.5\" />\n </button>\n )}\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-1.5 whitespace-nowrap text-xs text-muted-foreground\">\n <input\n type=\"checkbox\"\n className=\"size-3.5 cursor-pointer accent-primary\"\n checked={Boolean(field.pk)}\n disabled={!editable}\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n pk: event.target.checked || undefined,\n })\n }\n />\n PK\n </label>\n <label className=\"flex items-center gap-1.5 whitespace-nowrap text-xs text-muted-foreground\">\n <input\n type=\"checkbox\"\n className=\"size-3.5 cursor-pointer accent-primary\"\n checked={Boolean(field.nullable)}\n disabled={!editable}\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n nullable: event.target.checked || undefined,\n })\n }\n />\n Nullable\n </label>\n <DevInput\n className=\"h-7 flex-1 font-mono text-xs\"\n value={field.fk ?? \"\"}\n disabled={!editable}\n placeholder=\"FK → Entity.field\"\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n fk: event.target.value || undefined,\n })\n }\n />\n </div>\n {/* Diff row: change kind + the prior value (`was`) when the\n field is \"modified\", so before/after renders in Read. */}\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <DevSelect\n className=\"h-7 w-[120px] shrink-0\"\n value={field.change ?? \"none\"}\n disabled={!editable}\n onValueChange={(value) =>\n updateField(entityIndex, fieldIndex, {\n change:\n value === \"none\"\n ? undefined\n : (value as DataModelChange),\n // Drop a stale `was` when leaving the modified state.\n ...(value === \"modified\" ? {} : { was: undefined }),\n })\n }\n options={[\n { value: \"none\", label: \"No change\" },\n ...DATA_MODEL_CHANGES.map((change) => ({\n value: change,\n label: CHANGE_LABEL[change],\n })),\n ]}\n />\n {field.change === \"modified\" && (\n <DevInput\n className=\"h-7 flex-1 font-mono text-xs\"\n value={field.was ?? \"\"}\n disabled={!editable}\n placeholder=\"was (prior value, e.g. old type)\"\n onChange={(event) =>\n updateField(entityIndex, fieldIndex, {\n was: event.target.value || undefined,\n })\n }\n />\n )}\n </div>\n </div>\n ))}\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n className=\"flex w-fit items-center gap-1 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent/60 hover:text-foreground\"\n onClick={() => addField(entityIndex)}\n >\n <IconPlus className=\"size-3.5\" />\n Add field\n </button>\n )}\n </div>\n </div>\n ))}\n\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n className=\"flex items-center justify-center gap-1.5 rounded-md border border-dashed border-input py-2 text-sm text-muted-foreground hover:bg-accent/40 hover:text-foreground\"\n onClick={addEntity}\n >\n <IconPlus className=\"size-4\" />\n Add entity\n </button>\n )}\n </div>\n );\n}\n"]}
@@ -29,7 +29,7 @@ interface Change {
29
29
  * lines within a change region, matching jsdiff's ordering.
30
30
  */
31
31
  export declare function diffLines(before: string, after: string): Change[];
32
- declare function DiffRead({ data, blockId, title, summary }: BlockReadProps<DiffData>): import("react/jsx-runtime").JSX.Element;
32
+ declare function DiffRead({ data, blockId, title, summary, ctx, }: BlockReadProps<DiffData>): import("react/jsx-runtime").JSX.Element;
33
33
  declare function DiffEdit({ data, onChange, editable }: BlockEditProps<DiffData>): import("react/jsx-runtime").JSX.Element;
34
34
  export { DiffRead, DiffEdit };
35
35
  //# sourceMappingURL=DiffBlock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DiffBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/DiffBlock.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,kBAAkB,CAAC;AAG3D;;;;;;;;;;;;;;;GAeG;AAIH,UAAU,MAAM;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAqBD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAsEjE;AAwTD,iBAAS,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,cAAc,CAAC,QAAQ,CAAC,2CAqH5E;AAmPD,iBAAS,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,cAAc,CAAC,QAAQ,CAAC,2CA+EvE;AAED,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"DiffBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/DiffBlock.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAkB,QAAQ,EAAY,MAAM,kBAAkB,CAAC;AAW3E;;;;;;;;;;;;;;;GAeG;AAIH,UAAU,MAAM;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAqBD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAsEjE;AAyXD,iBAAS,QAAQ,CAAC,EAChB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,GACJ,EAAE,cAAc,CAAC,QAAQ,CAAC,2CA2N1B;AAiYD,iBAAS,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,cAAc,CAAC,QAAQ,CAAC,2CA2LvE;AAED,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC"}
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from "react";
3
- import { IconChevronRight, IconColumns, IconDotsVertical, IconFileDiff, IconList, } from "@tabler/icons-react";
3
+ import { IconChevronRight, IconColumns, IconDotsVertical, IconFileDiff, IconList, IconPlus, IconTrash, } from "@tabler/icons-react";
4
4
  import { common, createLowlight } from "lowlight";
5
5
  import { cn } from "../../utils.js";
6
+ import { AnnotationGutterMarker, AnnotationNoteRail, buildLineMarkerMap, hasRailAnnotations, resolveAnnotations, } from "./annotation-rail.js";
6
7
  import { DevInput, DevLabel, DevTextarea, DevSelect } from "./dev-doc-ui.js";
7
8
  /**
8
9
  * Split text into lines, each KEEPING its trailing newline (so the change
@@ -228,6 +229,19 @@ function SyntaxHighlightedLine({ code, language, }) {
228
229
  }, [code, language]);
229
230
  return _jsx(_Fragment, { children: highlighted ?? code });
230
231
  }
232
+ /** The default side an annotation targets when `side` is omitted. */
233
+ function annotationSide(annotation) {
234
+ return annotation.side === "before" ? "before" : "after";
235
+ }
236
+ /**
237
+ * Count 1-based source lines in a side's text, matching how `buildRows` numbers
238
+ * `oldNo`/`newNo`: a trailing newline does not add a phantom final line.
239
+ */
240
+ function countLines(text) {
241
+ if (text === "")
242
+ return 0;
243
+ return splitLines(text).length;
244
+ }
231
245
  /** Number of context lines above which an unchanged run is collapsed. */
232
246
  const COLLAPSE_THRESHOLD = 6;
233
247
  /** Context lines kept visible at each edge of a collapsed run. */
@@ -272,9 +286,32 @@ function buildRows(changes) {
272
286
  * Group rows into segments, collapsing interior runs of >COLLAPSE_THRESHOLD
273
287
  * context rows (keeping CONTEXT_EDGE visible at each side). Leading/trailing runs
274
288
  * collapse too, but keep only the inner edge visible.
289
+ *
290
+ * `isAnchored` marks context rows that carry an annotation (or sit adjacent to
291
+ * one): an anchored row is NEVER hidden inside a collapsed run, so a note that
292
+ * targets an unchanged line stays reachable. An anchor splits its run into the
293
+ * separately-collapsible spans on either side of it, with CONTEXT_EDGE rows kept
294
+ * visible around the anchor.
275
295
  */
276
- function segmentRows(rows) {
296
+ function segmentRows(rows, isAnchored) {
277
297
  const segments = [];
298
+ // Collapse one contiguous context run [from, to) that contains NO anchors.
299
+ const collapseRun = (run, atStart, atEnd) => {
300
+ if (run.length <= COLLAPSE_THRESHOLD) {
301
+ for (const row of run)
302
+ segments.push(row);
303
+ return;
304
+ }
305
+ const head = atStart ? [] : run.slice(0, CONTEXT_EDGE);
306
+ const tail = atEnd ? [] : run.slice(run.length - CONTEXT_EDGE);
307
+ const hidden = run.slice(head.length, run.length - tail.length);
308
+ for (const row of head)
309
+ segments.push(row);
310
+ if (hidden.length > 0)
311
+ segments.push({ collapsed: true, rows: hidden });
312
+ for (const row of tail)
313
+ segments.push(row);
314
+ };
278
315
  let i = 0;
279
316
  while (i < rows.length) {
280
317
  if (rows[i].kind !== "context") {
@@ -286,23 +323,29 @@ function segmentRows(rows) {
286
323
  let j = i;
287
324
  while (j < rows.length && rows[j].kind === "context")
288
325
  j += 1;
289
- const run = rows.slice(i, j);
290
- if (run.length <= COLLAPSE_THRESHOLD) {
291
- for (const row of run)
292
- segments.push(row);
326
+ const fullRun = rows.slice(i, j);
327
+ const runAtStart = i === 0;
328
+ const runAtEnd = j === rows.length;
329
+ if (!isAnchored || !fullRun.some(isAnchored)) {
330
+ collapseRun(fullRun, runAtStart, runAtEnd);
293
331
  }
294
332
  else {
295
- const atStart = i === 0;
296
- const atEnd = j === rows.length;
297
- const head = atStart ? [] : run.slice(0, CONTEXT_EDGE);
298
- const tail = atEnd ? [] : run.slice(run.length - CONTEXT_EDGE);
299
- const hidden = run.slice(head.length, run.length - tail.length);
300
- for (const row of head)
301
- segments.push(row);
302
- if (hidden.length > 0)
303
- segments.push({ collapsed: true, rows: hidden });
304
- for (const row of tail)
305
- segments.push(row);
333
+ // Walk the run, emitting anchored rows verbatim and collapsing the
334
+ // unanchored spans between them. An anchored row is always visible; the
335
+ // spans on each side of it collapse independently.
336
+ let spanStart = 0;
337
+ for (let k = 0; k <= fullRun.length; k += 1) {
338
+ const atAnchor = k < fullRun.length && isAnchored(fullRun[k]);
339
+ if (atAnchor || k === fullRun.length) {
340
+ const span = fullRun.slice(spanStart, k);
341
+ if (span.length > 0) {
342
+ collapseRun(span, runAtStart && spanStart === 0, runAtEnd && k === fullRun.length);
343
+ }
344
+ if (k < fullRun.length)
345
+ segments.push(fullRun[k]);
346
+ spanStart = k + 1;
347
+ }
348
+ }
306
349
  }
307
350
  i = j;
308
351
  }
@@ -345,20 +388,71 @@ function DiffLineText({ language, text }) {
345
388
  return (_jsx("span", { className: DIFF_LINE_CLASS, children: _jsx(SyntaxHighlightedLine, { code: code, language: language }) }));
346
389
  }
347
390
  /* ── Read ──────────────────────────────────────────────────────────────────── */
348
- function DiffRead({ data, blockId, title, summary }) {
391
+ function DiffRead({ data, blockId, title, summary, ctx, }) {
349
392
  const [mode, setMode] = useState(data.mode ?? "unified");
350
393
  const [expanded, setExpanded] = useState(() => new Set());
351
394
  const [showAllRows, setShowAllRows] = useState(false);
395
+ const [activeIndex, setActiveIndex] = useState(null);
352
396
  const rows = useMemo(() => buildRows(diffLines(data.before, data.after)), [data.before, data.after]);
353
397
  const language = useMemo(() => resolveDiffLanguage(data), [data.filename, data.language]);
354
398
  const fileParts = useMemo(() => splitDiffFilename(data.filename), [data.filename]);
355
399
  const splitLineCount = useMemo(() => pairSplitRows(rows).length, [rows]);
400
+ // Resolve annotations against the side they target. A `before` annotation's
401
+ // `lines` ref is clamped to the OLD file's line count and matched on `oldNo`;
402
+ // an `after` (default) ref to the NEW file and matched on `newNo`. Markers are
403
+ // authoring-order across BOTH sides so a note ↔ row ↔ rail card share one id.
404
+ const beforeLineCount = useMemo(() => countLines(data.before), [data.before]);
405
+ const afterLineCount = useMemo(() => countLines(data.after), [data.after]);
406
+ const resolved = useMemo(() => resolveAnnotations(data.annotations, (annotation) => annotationSide(annotation) === "before"
407
+ ? beforeLineCount
408
+ : afterLineCount), [data.annotations, beforeLineCount, afterLineCount]);
409
+ const hasAnnotations = hasRailAnnotations(resolved);
410
+ // Side-scoped line → markers maps so a row only lights from its own side.
411
+ const beforeMarkers = useMemo(() => buildLineMarkerMap(resolved.filter((r) => annotationSide(r.annotation) === "before")), [resolved]);
412
+ const afterMarkers = useMemo(() => buildLineMarkerMap(resolved.filter((r) => annotationSide(r.annotation) !== "before")), [resolved]);
413
+ const markersForRow = useMemo(() => {
414
+ return (row, side) => {
415
+ const out = [];
416
+ if ((side === undefined || side === "old") &&
417
+ row.oldNo != null &&
418
+ row.kind !== "added") {
419
+ out.push(...(beforeMarkers.get(row.oldNo) ?? []));
420
+ }
421
+ if ((side === undefined || side === "new") &&
422
+ row.newNo != null &&
423
+ row.kind !== "removed") {
424
+ out.push(...(afterMarkers.get(row.newNo) ?? []));
425
+ }
426
+ return out;
427
+ };
428
+ }, [beforeMarkers, afterMarkers]);
429
+ // A context row that carries a marker is an anchor: never collapse it away.
430
+ const anchoredRow = useMemo(() => {
431
+ if (!hasAnnotations)
432
+ return undefined;
433
+ return (row) => markersForRow(row).length > 0;
434
+ }, [hasAnnotations, markersForRow]);
356
435
  const added = rows.filter((r) => r.kind === "added").length;
357
436
  const removed = rows.filter((r) => r.kind === "removed").length;
358
437
  const unchanged = data.before === data.after;
359
438
  const totalVisibleLineCount = mode === "split" ? splitLineCount : rows.length;
360
439
  const shouldLimitRows = totalVisibleLineCount > DEFAULT_VISIBLE_DIFF_LINES;
361
- const rowLimit = !showAllRows && shouldLimitRows ? DEFAULT_VISIBLE_DIFF_LINES : undefined;
440
+ // Never truncate away an annotated row: extend the window past the last one.
441
+ const effectiveRowLimit = useMemo(() => {
442
+ if (showAllRows || !shouldLimitRows)
443
+ return undefined;
444
+ let limit = DEFAULT_VISIBLE_DIFF_LINES;
445
+ if (hasAnnotations) {
446
+ for (let idx = rows.length - 1; idx >= limit; idx -= 1) {
447
+ if (markersForRow(rows[idx]).length > 0) {
448
+ limit = idx + 1;
449
+ break;
450
+ }
451
+ }
452
+ }
453
+ return limit;
454
+ }, [showAllRows, shouldLimitRows, hasAnnotations, rows, markersForRow]);
455
+ const rowLimit = effectiveRowLimit;
362
456
  const displayedRows = mode === "unified" && rowLimit ? rows.slice(0, rowLimit) : rows;
363
457
  const toggleRun = (index) => setExpanded((prev) => {
364
458
  const next = new Set(prev);
@@ -368,31 +462,70 @@ function DiffRead({ data, blockId, title, summary }) {
368
462
  next.add(index);
369
463
  return next;
370
464
  });
371
- return (_jsxs("section", { className: "plan-block group/diff-block", "data-block-id": blockId, children: [title && _jsx("div", { className: "plan-block-label", children: title }), _jsxs("div", { className: "overflow-hidden rounded-md border border-border bg-background", children: [_jsxs("div", { className: "flex min-h-10 flex-wrap items-center gap-2 border-b border-border bg-muted/60 px-3 py-1.5", children: [_jsx(IconFileDiff, { className: "size-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "flex min-w-0 flex-1 items-baseline gap-1.5 font-mono", title: data.filename || undefined, children: [_jsx("span", { className: "min-w-0 max-w-[16rem] truncate text-[13px] font-semibold leading-5 text-foreground", children: fileParts.basename }), fileParts.directory && (_jsx("span", { className: "min-w-0 flex-1 truncate text-[11px] leading-5 text-muted-foreground/70", children: fileParts.directory }))] }), _jsxs("span", { className: "ml-1 flex shrink-0 items-center gap-2 font-mono text-xs", children: [_jsxs("span", { className: "text-emerald-700 dark:text-emerald-300", children: ["+", added] }), _jsxs("span", { className: "text-destructive", children: ["\u2212", removed] })] }), _jsxs("div", { className: "pointer-events-none ml-auto flex shrink-0 items-center overflow-hidden rounded-md border border-border bg-background opacity-0 transition-opacity group-hover/diff-block:pointer-events-auto group-hover/diff-block:opacity-100 group-focus-within/diff-block:pointer-events-auto group-focus-within/diff-block:opacity-100", children: [_jsx(ModeButton, { active: mode === "unified", onClick: () => setMode("unified"), icon: _jsx(IconList, { className: "size-3.5" }), label: "Unified" }), _jsx(ModeButton, { active: mode === "split", onClick: () => setMode("split"), icon: _jsx(IconColumns, { className: "size-3.5" }), label: "Split" })] })] }), unchanged ? (_jsx("div", { className: "px-4 py-6 text-center font-mono text-sm text-muted-foreground", children: "No changes" })) : mode === "split" ? (_jsx(SplitView, { rows: rows, language: language, rowLimit: rowLimit })) : (_jsx(UnifiedView, { rows: displayedRows, language: language, expanded: expanded, onToggleRun: toggleRun })), !unchanged && shouldLimitRows && (_jsxs("button", { type: "button", "data-plan-interactive": true, "aria-expanded": showAllRows, onClick: () => setShowAllRows((current) => !current), className: "flex h-7 w-full items-center justify-center gap-1.5 border-t border-border bg-background px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-muted/70 hover:text-foreground", children: [_jsx(IconChevronRight, { className: cn("size-3 shrink-0 transition-transform", showAllRows ? "-rotate-90" : "rotate-90") }), showAllRows
372
- ? "Show fewer"
373
- : `Show all ${totalVisibleLineCount} lines`] }))] }), summary && _jsx("p", { className: "mt-5 text-plan-muted", children: summary })] }));
465
+ return (_jsxs("section", { className: "plan-block group/diff-block", "data-block-id": blockId, children: [title && _jsx("div", { className: "plan-block-label", children: title }), summary && (_jsx("p", { className: "mb-3 text-sm leading-relaxed text-plan-muted", children: summary })), _jsxs("div", { className: cn(hasAnnotations &&
466
+ "grid items-start gap-3 md:grid-cols-[minmax(0,1fr)_minmax(190px,250px)]"), children: [_jsxs("div", { className: "overflow-hidden rounded-md border border-border bg-background", children: [_jsxs("div", { className: "flex min-h-10 flex-wrap items-center gap-2 border-b border-border bg-muted/60 px-3 py-1.5", children: [_jsx(IconFileDiff, { className: "size-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "flex min-w-0 flex-1 items-baseline gap-1.5 font-mono", title: data.filename || undefined, children: [_jsx("span", { className: "min-w-0 max-w-[16rem] truncate text-[13px] font-semibold leading-5 text-foreground", children: fileParts.basename }), fileParts.directory && (_jsx("span", { className: "min-w-0 flex-1 truncate text-[11px] leading-5 text-muted-foreground/70", children: fileParts.directory }))] }), _jsxs("span", { className: "ml-1 flex shrink-0 items-center gap-2 font-mono text-xs", children: [_jsxs("span", { className: "text-emerald-700 dark:text-emerald-300", children: ["+", added] }), _jsxs("span", { className: "text-destructive", children: ["\u2212", removed] })] }), _jsxs("div", { className: "pointer-events-none ml-auto flex shrink-0 items-center overflow-hidden rounded-md border border-border bg-background opacity-0 transition-opacity group-hover/diff-block:pointer-events-auto group-hover/diff-block:opacity-100 group-focus-within/diff-block:pointer-events-auto group-focus-within/diff-block:opacity-100", children: [_jsx(ModeButton, { active: mode === "unified", onClick: () => setMode("unified"), icon: _jsx(IconList, { className: "size-3.5" }), label: "Unified" }), _jsx(ModeButton, { active: mode === "split", onClick: () => setMode("split"), icon: _jsx(IconColumns, { className: "size-3.5" }), label: "Split" })] })] }), unchanged ? (_jsx("div", { className: "px-4 py-6 text-center font-mono text-sm text-muted-foreground", children: "No changes" })) : mode === "split" ? (_jsx(SplitView, { rows: rows, language: language, rowLimit: rowLimit, markersForRow: markersForRow, activeIndex: activeIndex, onActiveChange: setActiveIndex })) : (_jsx(UnifiedView, { rows: displayedRows, language: language, expanded: expanded, onToggleRun: toggleRun, markersForRow: markersForRow, anchoredRow: anchoredRow, activeIndex: activeIndex, onActiveChange: setActiveIndex })), !unchanged && shouldLimitRows && (_jsxs("button", { type: "button", "data-plan-interactive": true, "aria-expanded": showAllRows, onClick: () => setShowAllRows((current) => !current), className: "flex h-7 w-full items-center justify-center gap-1.5 border-t border-border bg-background px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-muted/70 hover:text-foreground", children: [_jsx(IconChevronRight, { className: cn("size-3 shrink-0 transition-transform", showAllRows ? "-rotate-90" : "rotate-90") }), showAllRows
467
+ ? "Show fewer"
468
+ : `Show all ${totalVisibleLineCount} lines`] }))] }), hasAnnotations && (_jsx(AnnotationNoteRail, { items: resolved, activeIndex: activeIndex, onActiveChange: setActiveIndex, ctx: ctx, showMarker: true }))] })] }));
374
469
  }
375
470
  function ModeButton({ active, onClick, icon, label, }) {
376
471
  return (_jsxs("button", { type: "button", "data-plan-interactive": true, onClick: onClick, "aria-pressed": active, className: cn("flex cursor-pointer items-center gap-1 px-2 py-1 text-xs font-medium transition-colors", active
377
472
  ? "bg-accent text-accent-foreground"
378
473
  : "text-muted-foreground hover:bg-muted/80 hover:text-foreground"), children: [icon, label] }));
379
474
  }
475
+ /**
476
+ * The numbered marker pip(s) for a row plus the active-state it derives. Returns
477
+ * `null` when the row carries no annotation so unannotated diffs render an empty
478
+ * marker column (or no column at all when the whole diff is unannotated).
479
+ */
480
+ function rowMarkerInfo(markers, activeIndex) {
481
+ if (markers.length === 0)
482
+ return null;
483
+ const isActive = markers.some((m) => m.index === activeIndex);
484
+ return { isActive, primaryIndex: markers[0].index };
485
+ }
486
+ /** Shared amber wash for an annotated row, brighter when active. */
487
+ function annotatedRowBg(info) {
488
+ if (!info)
489
+ return null;
490
+ return info.isActive
491
+ ? "bg-amber-400/20 dark:bg-amber-300/15"
492
+ : "bg-amber-400/[0.07] dark:bg-amber-300/[0.07]";
493
+ }
380
494
  /* ── Unified view ──────────────────────────────────────────────────────────── */
381
- function UnifiedView({ rows, language, expanded, onToggleRun, }) {
382
- const segments = useMemo(() => segmentRows(rows), [rows]);
495
+ function UnifiedView({ rows, language, expanded, onToggleRun, markersForRow, anchoredRow, activeIndex, onActiveChange, }) {
496
+ const segments = useMemo(() => segmentRows(rows, anchoredRow), [rows, anchoredRow]);
497
+ // Any annotation present ⇒ reserve the marker column so rows stay aligned.
498
+ const showMarkerColumn = useMemo(() => rows.some((row) => markersForRow(row).length > 0), [rows, markersForRow]);
499
+ const rowProps = {
500
+ language,
501
+ markersForRow,
502
+ activeIndex,
503
+ onActiveChange,
504
+ showMarkerColumn,
505
+ };
383
506
  let runIndex = 0;
384
507
  return (_jsx("div", { className: "overflow-x-auto", children: _jsx("div", { className: "w-max min-w-full font-mono text-[13px] leading-5", children: segments.map((segment, idx) => {
385
508
  if ("collapsed" in segment) {
386
509
  const key = runIndex++;
387
510
  const open = expanded.has(key);
388
511
  return (_jsxs("div", { children: [_jsx(CollapsedRow, { count: segment.rows.length, open: open, onClick: () => onToggleRun(key) }), open &&
389
- segment.rows.map((row, ri) => (_jsx(UnifiedRow, { row: row, language: language }, `run-${key}-${ri}`)))] }, `run-${key}`));
512
+ segment.rows.map((row, ri) => (_jsx(UnifiedRow, { row: row, ...rowProps }, `run-${key}-${ri}`)))] }, `run-${key}`));
390
513
  }
391
- return _jsx(UnifiedRow, { row: segment, language: language }, idx);
514
+ return _jsx(UnifiedRow, { row: segment, ...rowProps }, idx);
392
515
  }) }) }));
393
516
  }
394
- function UnifiedRow({ language, row }) {
395
- return (_jsxs("div", { className: cn("flex min-h-5 min-w-full", ROW_BG[row.kind]), children: [_jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]"), children: row.oldNo ?? "" }), _jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]"), children: row.newNo ?? "" }), _jsx("span", { className: cn("w-6 shrink-0 select-none py-0 text-center font-semibold leading-5", GUTTER_BG[row.kind], SIGN_COLOR[row.kind]), children: SIGN[row.kind] }), _jsx(DiffLineText, { text: row.text, language: language })] }));
517
+ function UnifiedRow({ language, row, markersForRow, activeIndex, onActiveChange, showMarkerColumn, }) {
518
+ const markers = markersForRow(row);
519
+ const info = rowMarkerInfo(markers, activeIndex);
520
+ return (_jsxs("div", { className: cn("flex min-h-5 min-w-full", ROW_BG[row.kind], annotatedRowBg(info)), onMouseEnter: info ? () => onActiveChange(info.primaryIndex) : undefined, onMouseLeave: info ? () => onActiveChange(null) : undefined, children: [_jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]"), children: row.oldNo ?? "" }), _jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]"), children: row.newNo ?? "" }), _jsx("span", { className: cn("w-6 shrink-0 select-none py-0 text-center font-semibold leading-5", GUTTER_BG[row.kind], SIGN_COLOR[row.kind]), children: SIGN[row.kind] }), showMarkerColumn && (_jsx(MarkerCell, { marker: markers[0]?.marker, info: info })), _jsx(DiffLineText, { text: row.text, language: language })] }));
521
+ }
522
+ /**
523
+ * The fixed-width marker column rendered between the sign gutter and the code.
524
+ * When `marker` is undefined the cell is an empty spacer so every row in a diff
525
+ * with annotations keeps its code text aligned.
526
+ */
527
+ function MarkerCell({ marker, info, }) {
528
+ return (_jsx("span", { className: "flex w-6 shrink-0 select-none items-center justify-center py-0", children: marker != null && info != null && (_jsx(AnnotationGutterMarker, { marker: marker, active: info.isActive })) }));
396
529
  }
397
530
  function CollapsedRow({ count, open, onClick, }) {
398
531
  return (_jsxs("button", { type: "button", "data-plan-interactive": true, onClick: onClick, className: "flex w-full cursor-pointer items-center gap-2 border-y border-border bg-muted/70 px-3 py-1 text-left text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: [_jsx(IconDotsVertical, { className: "size-3.5 shrink-0" }), _jsxs("span", { children: [open ? "Hide" : "Show", " ", count, " unchanged line", count === 1 ? "" : "s"] })] }));
@@ -426,28 +559,56 @@ function pairSplitRows(rows) {
426
559
  }
427
560
  return out;
428
561
  }
429
- function SplitView({ language, rowLimit, rows, }) {
562
+ function SplitView({ language, rowLimit, rows, markersForRow, activeIndex, onActiveChange, }) {
430
563
  const pairs = useMemo(() => pairSplitRows(rows), [rows]);
431
564
  const displayedPairs = rowLimit ? pairs.slice(0, rowLimit) : pairs;
432
- return (_jsxs("div", { className: "flex w-full bg-background font-mono text-[12px] leading-5", children: [_jsx("div", { className: "min-w-0 flex-1 overflow-x-auto border-r border-border", children: _jsx("div", { className: "inline-block min-w-full", children: displayedPairs.map((pair, idx) => (_jsx(SplitCell, { row: pair.left, side: "old", language: language }, `old-${idx}`))) }) }), _jsx("div", { className: "min-w-0 flex-1 overflow-x-auto", children: _jsx("div", { className: "inline-block min-w-full", children: displayedPairs.map((pair, idx) => (_jsx(SplitCell, { row: pair.right, side: "new", language: language }, `new-${idx}`))) }) })] }));
565
+ // Reserve the marker column on a side only if any visible row there has one.
566
+ const showOldMarkers = useMemo(() => displayedPairs.some((pair) => pair.left && markersForRow(pair.left, "old").length > 0), [displayedPairs, markersForRow]);
567
+ const showNewMarkers = useMemo(() => displayedPairs.some((pair) => pair.right && markersForRow(pair.right, "new").length > 0), [displayedPairs, markersForRow]);
568
+ const cellProps = { language, markersForRow, activeIndex, onActiveChange };
569
+ return (_jsxs("div", { className: "flex w-full bg-background font-mono text-[12px] leading-5", children: [_jsx("div", { className: "min-w-0 flex-1 overflow-x-auto border-r border-border", children: _jsx("div", { className: "inline-block min-w-full", children: displayedPairs.map((pair, idx) => (_jsx(SplitCell, { row: pair.left, side: "old", showMarkerColumn: showOldMarkers, ...cellProps }, `old-${idx}`))) }) }), _jsx("div", { className: "min-w-0 flex-1 overflow-x-auto", children: _jsx("div", { className: "inline-block min-w-full", children: displayedPairs.map((pair, idx) => (_jsx(SplitCell, { row: pair.right, side: "new", showMarkerColumn: showNewMarkers, ...cellProps }, `new-${idx}`))) }) })] }));
433
570
  }
434
- function SplitCell({ language, row, side, }) {
571
+ function SplitCell({ language, row, side, markersForRow, activeIndex, onActiveChange, showMarkerColumn, }) {
435
572
  if (!row) {
436
- return (_jsxs("div", { className: "flex min-h-5 min-w-full bg-muted/40 opacity-70", children: [_jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]") }), _jsx("span", { className: "w-6 shrink-0 bg-muted/60" }), _jsx("span", { className: DIFF_LINE_CLASS, children: " " })] }));
573
+ return (_jsxs("div", { className: "flex min-h-5 min-w-full bg-muted/40 opacity-70", children: [_jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]") }), _jsx("span", { className: "w-6 shrink-0 bg-muted/60" }), showMarkerColumn && _jsx("span", { className: "w-6 shrink-0" }), _jsx("span", { className: DIFF_LINE_CLASS, children: " " })] }));
437
574
  }
438
575
  const sign = side === "old" ? "−" : "+";
439
576
  const showSign = row.kind !== "context";
440
- return (_jsxs("div", { className: cn("flex min-h-5 min-w-full", ROW_BG[row.kind]), children: [_jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]"), children: side === "old" ? (row.oldNo ?? "") : (row.newNo ?? "") }), _jsx("span", { className: cn("w-6 shrink-0 select-none py-0 text-center font-semibold leading-5", GUTTER_BG[row.kind], SIGN_COLOR[row.kind]), children: showSign ? sign : " " }), _jsx(DiffLineText, { text: row.text, language: language })] }));
577
+ const markers = markersForRow(row, side);
578
+ const info = rowMarkerInfo(markers, activeIndex);
579
+ return (_jsxs("div", { className: cn("flex min-h-5 min-w-full", ROW_BG[row.kind], annotatedRowBg(info)), onMouseEnter: info ? () => onActiveChange(info.primaryIndex) : undefined, onMouseLeave: info ? () => onActiveChange(null) : undefined, children: [_jsx("span", { className: cn(LINE_NO_CLASS, "w-[52px]"), children: side === "old" ? (row.oldNo ?? "") : (row.newNo ?? "") }), _jsx("span", { className: cn("w-6 shrink-0 select-none py-0 text-center font-semibold leading-5", GUTTER_BG[row.kind], SIGN_COLOR[row.kind]), children: showSign ? sign : " " }), showMarkerColumn && (_jsx(MarkerCell, { marker: markers[0]?.marker, info: info })), _jsx(DiffLineText, { text: row.text, language: language })] }));
441
580
  }
442
581
  /* ── Edit (panel) ──────────────────────────────────────────────────────────── */
443
582
  const codeAreaClass = "min-h-[140px] font-mono text-xs leading-5";
444
583
  function DiffEdit({ data, onChange, editable }) {
445
584
  const patch = (next) => onChange({ ...data, ...next });
446
585
  const mode = data.mode ?? "unified";
586
+ const annotations = data.annotations ?? [];
587
+ const updateAnnotation = (index, next) => patch({
588
+ annotations: annotations.map((annotation, i) => i === index ? { ...annotation, ...next } : annotation),
589
+ });
590
+ const removeAnnotation = (index) => patch({ annotations: annotations.filter((_, i) => i !== index) });
591
+ const addAnnotation = () => {
592
+ if (annotations.length >= 80)
593
+ return; // schema max
594
+ patch({
595
+ annotations: [
596
+ ...annotations,
597
+ { side: "after", lines: "1", label: "", note: "" },
598
+ ],
599
+ });
600
+ };
447
601
  return (_jsxs("div", { className: "flex flex-col gap-3", "data-plan-interactive": true, children: [_jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-filename", className: "text-xs", children: "Filename" }), _jsx(DevInput, { id: "diff-filename", value: data.filename ?? "", placeholder: "src/add.ts", disabled: !editable, onChange: (event) => patch({ filename: event.target.value || undefined }) })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-language", className: "text-xs", children: "Language" }), _jsx(DevInput, { id: "diff-language", value: data.language ?? "", placeholder: "ts", disabled: !editable, onChange: (event) => patch({ language: event.target.value || undefined }) })] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { className: "text-xs", children: "Layout" }), _jsx(DevSelect, { value: mode, disabled: !editable, onValueChange: (value) => patch({ mode: value }), options: [
448
602
  { value: "unified", label: "Unified" },
449
603
  { value: "split", label: "Split (side-by-side)" },
450
- ] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-before", className: "text-xs", children: "Before" }), _jsx(DevTextarea, { id: "diff-before", spellCheck: false, className: codeAreaClass, value: data.before, disabled: !editable, onChange: (event) => patch({ before: event.target.value }) })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-after", className: "text-xs", children: "After" }), _jsx(DevTextarea, { id: "diff-after", spellCheck: false, className: codeAreaClass, value: data.after, disabled: !editable, onChange: (event) => patch({ after: event.target.value }) })] })] }));
604
+ ] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-before", className: "text-xs", children: "Before" }), _jsx(DevTextarea, { id: "diff-before", spellCheck: false, className: codeAreaClass, value: data.before, disabled: !editable, onChange: (event) => patch({ before: event.target.value }) })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(DevLabel, { htmlFor: "diff-after", className: "text-xs", children: "After" }), _jsx(DevTextarea, { id: "diff-after", spellCheck: false, className: codeAreaClass, value: data.after, disabled: !editable, onChange: (event) => patch({ after: event.target.value }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(DevLabel, { className: "text-xs", children: "Annotations" }), editable && annotations.length < 80 && (_jsxs("button", { type: "button", "data-plan-interactive": true, onClick: addAnnotation, className: "flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-plan-muted transition-colors hover:bg-plan-block/60 hover:text-plan-text", children: [_jsx(IconPlus, { className: "size-3.5" }), "Add annotation"] }))] }), annotations.length === 0 && (_jsx("p", { className: "text-xs text-plan-muted", children: "No annotations yet. Add one to anchor a note to a line range on the before or after side." })), annotations.map((annotation, index) => (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-plan-line bg-plan-block/30 p-2", children: [_jsxs("div", { className: "grid gap-2 sm:grid-cols-[110px_110px_minmax(0,1fr)_auto]", children: [_jsx(DevSelect, { "aria-label": `Annotation ${index + 1} side`, value: annotation.side ?? "after", disabled: !editable, onValueChange: (value) => updateAnnotation(index, {
605
+ side: value,
606
+ }), options: [
607
+ { value: "after", label: "After" },
608
+ { value: "before", label: "Before" },
609
+ ] }), _jsx(DevInput, { "aria-label": `Annotation ${index + 1} lines`, value: annotation.lines, placeholder: "3-5", disabled: !editable, onChange: (event) => updateAnnotation(index, { lines: event.target.value }) }), _jsx(DevInput, { "aria-label": `Annotation ${index + 1} label`, value: annotation.label ?? "", placeholder: "Label (optional)", disabled: !editable, onChange: (event) => updateAnnotation(index, {
610
+ label: event.target.value || undefined,
611
+ }) }), editable && (_jsx("button", { type: "button", "data-plan-interactive": true, "aria-label": `Remove annotation ${index + 1}`, onClick: () => removeAnnotation(index), className: "flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-md text-plan-muted transition-colors hover:bg-muted hover:text-foreground", children: _jsx(IconTrash, { className: "size-4" }) }))] }), _jsx(DevTextarea, { "aria-label": `Annotation ${index + 1} note`, className: "min-h-[60px] text-sm", value: annotation.note, placeholder: "Explain what these lines do\u2026", disabled: !editable, onChange: (event) => updateAnnotation(index, { note: event.target.value }) })] }, index)))] })] }));
451
612
  }
452
613
  export { DiffRead, DiffEdit };
453
614
  //# sourceMappingURL=DiffBlock.js.map