From d513f93c84ae985738e0f696fcb72fa1153046ef Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Tue, 28 Apr 2026 17:35:08 +0100
Subject: feat: document UI with contract blocks, expandable logs, and
 interaction controls (#97)

* feat: soryu-co/soryu - makima: Rename tasks to contracts in directive API and types

* feat: soryu-co/soryu - makima: Add contract interaction panel with comment and interrupt

* feat: soryu-co/soryu - makima: Build expandable contract log feed in StepsDiagram

* feat: soryu-co/soryu - makima: Rename tasks to contracts throughout document UI and add contract block support

* feat: soryu-co/soryu - makima: Add comment and interrupt controls to expanded step log feed

* feat: soryu-co/soryu - makima: Audit and fix Document UI feature flag visibility and missing implementations

* feat: soryu-co/soryu - makima: Add expandable step rows with live log feed in StepsDiagram

* WIP: heartbeat checkpoint

* feat: soryu-co/soryu - makima: Integrate all document UI components and final polish
---
 .../document/nodes/ContractBlockNode.tsx           | 106 +++++++++++++++++++++
 1 file changed, 106 insertions(+)
 create mode 100644 frontend/src/components/document/nodes/ContractBlockNode.tsx

(limited to 'frontend/src/components/document/nodes/ContractBlockNode.tsx')

diff --git a/frontend/src/components/document/nodes/ContractBlockNode.tsx b/frontend/src/components/document/nodes/ContractBlockNode.tsx
new file mode 100644
index 0000000..86e4c9d
--- /dev/null
+++ b/frontend/src/components/document/nodes/ContractBlockNode.tsx
@@ -0,0 +1,106 @@
+import {
+  DecoratorNode,
+  DOMExportOutput,
+  LexicalNode,
+  NodeKey,
+  SerializedLexicalNode,
+  Spread,
+} from 'lexical';
+import React from 'react';
+import { ContractBlockComponent } from './ContractBlockComponent';
+
+export type SerializedContractBlockNode = Spread<
+  {
+    contractId: string;
+    contractName: string;
+  },
+  SerializedLexicalNode
+>;
+
+export class ContractBlockNode extends DecoratorNode<JSX.Element> {
+  __contractId: string;
+  __contractName: string;
+
+  static getType(): string {
+    return 'contract-block';
+  }
+
+  static clone(node: ContractBlockNode): ContractBlockNode {
+    return new ContractBlockNode(node.__contractId, node.__contractName, node.__key);
+  }
+
+  constructor(contractId: string, contractName: string, key?: NodeKey) {
+    super(key);
+    this.__contractId = contractId;
+    this.__contractName = contractName;
+  }
+
+  createDOM(): HTMLElement {
+    const div = document.createElement('div');
+    div.className = 'contract-block-wrapper';
+    return div;
+  }
+
+  updateDOM(): boolean {
+    return false;
+  }
+
+  decorate(): JSX.Element {
+    return (
+      <ContractBlockComponent
+        contractId={this.__contractId}
+        contractName={this.__contractName}
+      />
+    );
+  }
+
+  exportJSON(): SerializedContractBlockNode {
+    return {
+      ...super.exportJSON(),
+      type: 'contract-block',
+      contractId: this.__contractId,
+      contractName: this.__contractName,
+      version: 1,
+    };
+  }
+
+  static importJSON(serializedNode: SerializedContractBlockNode): ContractBlockNode {
+    return $createContractBlockNode(
+      serializedNode.contractId,
+      serializedNode.contractName
+    );
+  }
+
+  isInline(): boolean {
+    return false;
+  }
+
+  canInsertTextBefore(): boolean {
+    return false;
+  }
+
+  canInsertTextAfter(): boolean {
+    return false;
+  }
+
+  exportDOM(): DOMExportOutput {
+    const element = document.createElement('div');
+    element.className = 'contract-block-wrapper';
+    element.setAttribute('data-contract-id', this.__contractId);
+    element.textContent = `[Contract: ${this.__contractName}]`;
+    return { element };
+  }
+}
+
+export function $createContractBlockNode(
+  contractId: string,
+  contractName: string
+): ContractBlockNode {
+  return new ContractBlockNode(contractId, contractName);
+}
+
+export function $isContractBlockNode(
+  node: LexicalNode | null | undefined,
+): node is ContractBlockNode {
+  return node instanceof ContractBlockNode;
+}
-- 
cgit v1.2.3

