From d513f93c84ae985738e0f696fcb72fa1153046ef Mon Sep 17 00:00:00 2001 From: soryu 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 { + __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 ( + + ); + } + + 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