Parcourir la source

perf(docs): make quick start section easier to understand & beautify homepage (#952)

* chore(docs): update logo

* docs(intro): optimize introduction document

* docs(start): rename getting-started to quick-start

* docs(start): update quick start install

* docs(start): update quick start free layout

* refactor(docs): move examples to components

* docs(intro): interactive pin to bottom

* refactor(docs): getting started file struct adjust

* docs(start): add quick taste link

* fix(docs): landing page link

* docs: add contribution guide

* fix(docs): navigation link

* docs(guide): en contributing doc

* docs(start): en quick start doc

* docs(start): en start free layout doc

* feat(docs): fixed layout quick start examples

* feat(docs): fixed layout examples fitview & more nodes

* docs(start): start fixed layout doc

* docs(start): en start fixed layout doc

* chore: custom homepage layout

* feat(docs): landing page flowgram logo

* feat(docs): landing page meteor background

* chore(docs): remove runtime wip tag

* fix(docs): landing page logo error picture

* fix: build error

* fix: build error

* chore: remove example file header

* fix: build error

* feat(docs): landing page logo animation

* perf(docs): optimize background animation performance with adaptive quality settings

* fix: nossr

* chore: license header ignore path

* fix: build error

* chore(demo): reset initial data

* docs(intro): update introduction

* chore: upgrade github node version

* fix(core): free layout core support ssg

---------

Co-authored-by: dragooncjw <289056872@qq.com>
Louis Young il y a 2 mois
Parent
commit
a8da523e5f
100 fichiers modifiés avec 6079 ajouts et 2014 suppressions
  1. 1 1
      .github/workflows/ci.yml
  2. 1 1
      .github/workflows/common-pr-checks.yml
  3. 1 1
      .github/workflows/deploy.yml
  4. 1 1
      .github/workflows/e2e.yml
  5. 1 1
      .github/workflows/publish-alpha.yml
  6. 1 1
      .github/workflows/publish-app.yml
  7. 1 1
      .github/workflows/publish-minor.yml
  8. 1 1
      .github/workflows/publish.yml
  9. 1 1
      .github/workflows/sync-screenshot.yml
  10. 3 0
      .vscode/settings.json
  11. 54 0
      apps/docs/components/code-preview/index.tsx
  12. 15 0
      apps/docs/components/fixed-examples/step-1.tsx
  13. 79 0
      apps/docs/components/fixed-examples/step-2.tsx
  14. 206 0
      apps/docs/components/fixed-examples/step-3.tsx
  15. 212 0
      apps/docs/components/fixed-examples/step-4.tsx
  16. 91 0
      apps/docs/components/fixed-examples/step-5/adder.tsx
  17. 21 0
      apps/docs/components/fixed-examples/step-5/app.tsx
  18. 23 0
      apps/docs/components/fixed-examples/step-5/initial-data.ts
  19. 12 0
      apps/docs/components/fixed-examples/step-5/node-registries.tsx
  20. 80 0
      apps/docs/components/fixed-examples/step-5/node-render.tsx
  21. 39 0
      apps/docs/components/fixed-examples/step-5/use-editor-props.tsx
  22. 101 0
      apps/docs/components/fixed-examples/step-6/adder.tsx
  23. 21 0
      apps/docs/components/fixed-examples/step-6/app.tsx
  24. 81 0
      apps/docs/components/fixed-examples/step-6/initial-data.ts
  25. 79 0
      apps/docs/components/fixed-examples/step-6/node-registries.tsx
  26. 80 0
      apps/docs/components/fixed-examples/step-6/node-render.tsx
  27. 81 0
      apps/docs/components/fixed-examples/step-6/use-editor-props.tsx
  28. 101 0
      apps/docs/components/fixed-examples/step-7/adder.tsx
  29. 25 0
      apps/docs/components/fixed-examples/step-7/app.tsx
  30. 81 0
      apps/docs/components/fixed-examples/step-7/initial-data.ts
  31. 35 0
      apps/docs/components/fixed-examples/step-7/minimap.tsx
  32. 79 0
      apps/docs/components/fixed-examples/step-7/node-registries.tsx
  33. 80 0
      apps/docs/components/fixed-examples/step-7/node-render.tsx
  34. 85 0
      apps/docs/components/fixed-examples/step-7/tools.tsx
  35. 87 0
      apps/docs/components/fixed-examples/step-7/use-editor-props.tsx
  36. 10 0
      apps/docs/components/free-examples/step-1.tsx
  37. 65 0
      apps/docs/components/free-examples/step-2.tsx
  38. 82 0
      apps/docs/components/free-examples/step-3.tsx
  39. 126 0
      apps/docs/components/free-examples/step-4.tsx
  40. 16 0
      apps/docs/components/free-examples/step-5/app.tsx
  41. 67 0
      apps/docs/components/free-examples/step-5/initial-data.ts
  42. 11 0
      apps/docs/components/free-examples/step-5/node-registries.tsx
  43. 34 0
      apps/docs/components/free-examples/step-5/node-render.tsx
  44. 27 0
      apps/docs/components/free-examples/step-5/use-editor-props.tsx
  45. 16 0
      apps/docs/components/free-examples/step-6/app.tsx
  46. 82 0
      apps/docs/components/free-examples/step-6/initial-data.ts
  47. 30 0
      apps/docs/components/free-examples/step-6/node-registries.tsx
  48. 34 0
      apps/docs/components/free-examples/step-6/node-render.tsx
  49. 58 0
      apps/docs/components/free-examples/step-6/use-editor-props.tsx
  50. 43 0
      apps/docs/components/free-examples/step-7/add-node.tsx
  51. 22 0
      apps/docs/components/free-examples/step-7/app.tsx
  52. 82 0
      apps/docs/components/free-examples/step-7/initial-data.ts
  53. 30 0
      apps/docs/components/free-examples/step-7/minimap.tsx
  54. 30 0
      apps/docs/components/free-examples/step-7/node-registries.tsx
  55. 34 0
      apps/docs/components/free-examples/step-7/node-render.tsx
  56. 90 0
      apps/docs/components/free-examples/step-7/tools.tsx
  57. 68 0
      apps/docs/components/free-examples/step-7/use-editor-props.tsx
  58. 11 7
      apps/docs/global.less
  59. 8 7
      apps/docs/rspress.config.ts
  60. 1 1
      apps/docs/src/en/_nav.json
  61. 4 4
      apps/docs/src/en/guide/_meta.json
  62. 0 3
      apps/docs/src/en/guide/contact-us.mdx
  63. 112 0
      apps/docs/src/en/guide/contributing.mdx
  64. 65 2
      apps/docs/src/en/guide/free-layout/node.mdx
  65. 4 3
      apps/docs/src/en/guide/getting-started/_meta.json
  66. 0 365
      apps/docs/src/en/guide/getting-started/create-fixed-layout-simple.mdx
  67. 0 339
      apps/docs/src/en/guide/getting-started/create-free-layout-simple.mdx
  68. 370 0
      apps/docs/src/en/guide/getting-started/fixed-layout.mdx
  69. 302 0
      apps/docs/src/en/guide/getting-started/free-layout.mdx
  70. 0 29
      apps/docs/src/en/guide/getting-started/install.mdx
  71. 160 0
      apps/docs/src/en/guide/getting-started/introduction.mdx
  72. 100 0
      apps/docs/src/en/guide/getting-started/quick-start.mdx
  73. 0 248
      apps/docs/src/en/guide/introduction.mdx
  74. 4 4
      apps/docs/src/en/index.md
  75. BIN
      apps/docs/src/public/examples/example-fixed-layout-simple.png
  76. BIN
      apps/docs/src/public/examples/example-fixed-layout.png
  77. BIN
      apps/docs/src/public/examples/example-free-layout-simple.png
  78. BIN
      apps/docs/src/public/examples/example-free-layout.png
  79. 56 0
      apps/docs/src/public/flowgram-avatar.svg
  80. 55 0
      apps/docs/src/public/flowgram-logo.svg
  81. 3 0
      apps/docs/src/public/transparent-logo.svg
  82. 1 1
      apps/docs/src/zh/_nav.json
  83. 5 5
      apps/docs/src/zh/guide/_meta.json
  84. 112 0
      apps/docs/src/zh/guide/contributing.mdx
  85. 69 1
      apps/docs/src/zh/guide/free-layout/node.mdx
  86. 4 3
      apps/docs/src/zh/guide/getting-started/_meta.json
  87. 0 372
      apps/docs/src/zh/guide/getting-started/create-fixed-layout-simple.mdx
  88. 0 349
      apps/docs/src/zh/guide/getting-started/create-free-layout-simple.mdx
  89. 378 0
      apps/docs/src/zh/guide/getting-started/fixed-layout.mdx
  90. 300 0
      apps/docs/src/zh/guide/getting-started/free-layout.mdx
  91. 0 32
      apps/docs/src/zh/guide/getting-started/install.mdx
  92. 165 0
      apps/docs/src/zh/guide/getting-started/introduction.mdx
  93. 100 0
      apps/docs/src/zh/guide/getting-started/quick-start.mdx
  94. 0 226
      apps/docs/src/zh/guide/introduction.mdx
  95. 4 4
      apps/docs/src/zh/index.md
  96. 39 0
      apps/docs/theme/components/background/index.css
  97. 604 0
      apps/docs/theme/components/background/index.tsx
  98. 50 0
      apps/docs/theme/components/logo/index.less
  99. 23 0
      apps/docs/theme/components/logo/index.tsx
  100. 53 0
      apps/docs/theme/components/logo/initial-data.ts

+ 1 - 1
.github/workflows/ci.yml

@@ -23,7 +23,7 @@ jobs:
           ls -alh
       - uses: actions/setup-node@v3
         with:
-          node-version: 18
+          node-version: 22
       # - name: Verify Change Logs
       #   run: node common/scripts/install-run-rush.js change --verify
       - name: Rush Install

+ 1 - 1
.github/workflows/common-pr-checks.yml

@@ -19,7 +19,7 @@ jobs:
 
       - uses: actions/setup-node@v3
         with:
-          node-version: 18
+          node-version: 22
 
       - name: Install Dependencies
         run: node common/scripts/install-run-rush.js install

+ 1 - 1
.github/workflows/deploy.yml

@@ -26,7 +26,7 @@ jobs:
 
       - uses: actions/setup-node@v3
         with:
-          node-version: 18
+          node-version: 22
           registry-url: 'https://registry.npmjs.org/'
 
       - name: Rush Install

+ 1 - 1
.github/workflows/e2e.yml

@@ -15,7 +15,7 @@ jobs:
 
       - uses: actions/setup-node@v3
         with:
-          node-version: 18
+          node-version: 22
 
       - name: Rush Install
         run: node common/scripts/install-run-rush.js install

+ 1 - 1
.github/workflows/publish-alpha.yml

@@ -46,7 +46,7 @@ jobs:
           echo "The package output version is ${{ steps.get_version.outputs.version }}"
       - uses: actions/setup-node@v3
         with:
-          node-version: 18
+          node-version: 22
           registry-url: 'https://registry.npmjs.org/'
       - name: Rush Install
         run: node common/scripts/install-run-rush.js install

+ 1 - 1
.github/workflows/publish-app.yml

@@ -38,7 +38,7 @@ jobs:
           echo "The package output version is ${{ steps.get_version.outputs.version }}"
       - uses: actions/setup-node@v3
         with:
-          node-version: 18
+          node-version: 22
           registry-url: 'https://registry.npmjs.org/'
       - name: Rush Install
         run: node common/scripts/install-run-rush.js install

+ 1 - 1
.github/workflows/publish-minor.yml

@@ -40,7 +40,7 @@ jobs:
           echo "The package output version is ${{ steps.get_version.outputs.version }}"
       - uses: actions/setup-node@v3
         with:
-          node-version: 18
+          node-version: 22
           registry-url: 'https://registry.npmjs.org/'
       - name: Rush Install
         run: node common/scripts/install-run-rush.js install

+ 1 - 1
.github/workflows/publish.yml

@@ -40,7 +40,7 @@ jobs:
           echo "The package output version is ${{ steps.get_version.outputs.version }}"
       - uses: actions/setup-node@v3
         with:
-          node-version: 18
+          node-version: 22
           registry-url: 'https://registry.npmjs.org/'
       - name: Rush Install
         run: node common/scripts/install-run-rush.js install

+ 1 - 1
.github/workflows/sync-screenshot.yml

@@ -17,7 +17,7 @@ jobs:
 
       - uses: actions/setup-node@v3
         with:
-          node-version: 18
+          node-version: 22
 
       - name: Set Git user.name and user.email from trigger actor
         run: |

+ 3 - 0
.vscode/settings.json

@@ -138,5 +138,8 @@
   },
   "[svg]": {
     "editor.defaultFormatter": "jock.svg"
+  },
+  "[mdx]": {
+    "editor.defaultFormatter": "unifiedjs.vscode-mdx"
   }
 }

+ 54 - 0
apps/docs/components/code-preview/index.tsx

@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { FC } from 'react';
+
+import { Sandpack } from '@codesandbox/sandpack-react';
+
+interface CodePreviewProps {
+  files: Record<string, string>;
+  activeFile?: string;
+}
+
+export const CodePreview: FC<CodePreviewProps> = ({ files, activeFile }) => (
+  <Sandpack
+    files={files}
+    theme="light"
+    template="react-ts"
+    customSetup={{
+      dependencies: {
+        '@flowgram.ai/free-layout-editor': '0.5.5',
+        '@flowgram.ai/free-snap-plugin': '0.5.5',
+        '@flowgram.ai/minimap-plugin': '0.5.5',
+        'styled-components': '5.3.11',
+      },
+    }}
+    options={{
+      editorHeight: 350,
+      activeFile,
+    }}
+  />
+);
+
+export const FixedLayoutCodePreview: FC<CodePreviewProps> = ({ files, activeFile }) => (
+  <Sandpack
+    files={files}
+    theme="light"
+    template="react-ts"
+    customSetup={{
+      dependencies: {
+        '@flowgram.ai/fixed-layout-editor': '0.1.0-alpha.19',
+        // 为了解决semi无法在sandpack使用的问题,单独发了包,将semi打包进@flowgram.ai/fixed-semi-materials中
+        '@flowgram.ai/fixed-semi-materials': '0.1.0-alpha.19',
+        '@flowgram.ai/minimap-plugin': '0.1.0-alpha.19',
+        'styled-components': '5.3.11',
+      },
+    }}
+    options={{
+      editorHeight: 350,
+      activeFile,
+    }}
+  />
+);

+ 15 - 0
apps/docs/components/fixed-examples/step-1.tsx

@@ -0,0 +1,15 @@
+import '@flowgram.ai/fixed-layout-editor/index.css';
+import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
+import { FixedLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/fixed-layout-editor';
+
+const FlowGramApp = () => (
+  <FixedLayoutEditorProvider
+    materials={{
+      components: defaultFixedSemiMaterials,
+    }}
+  >
+    <EditorRenderer />
+  </FixedLayoutEditorProvider>
+);
+
+export default FlowGramApp;

+ 79 - 0
apps/docs/components/fixed-examples/step-2.tsx

@@ -0,0 +1,79 @@
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
+import {
+  FixedLayoutEditorProvider,
+  EditorRenderer,
+  FlowNodeEntity,
+  useNodeRender,
+} from '@flowgram.ai/fixed-layout-editor';
+
+export const NodeRender = ({ node }: { node: FlowNodeEntity }) => {
+  const {
+    onMouseEnter,
+    onMouseLeave,
+    startDrag,
+    form,
+    dragging,
+    isBlockOrderIcon,
+    isBlockIcon,
+    activated,
+  } = useNodeRender();
+
+  return (
+    <div
+      onMouseEnter={onMouseEnter}
+      onMouseLeave={onMouseLeave}
+      onMouseDown={(e) => {
+        startDrag(e);
+        e.stopPropagation();
+      }}
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: activated ? '#82a7fc' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+        opacity: dragging ? 0.3 : 1,
+        ...(isBlockOrderIcon || isBlockIcon ? { width: 260 } : {}),
+      }}
+    >
+      {form?.render()}
+    </div>
+  );
+};
+
+const FlowGramApp = () => (
+  <FixedLayoutEditorProvider
+    nodeRegistries={[
+      {
+        type: 'custom',
+      },
+    ]}
+    initialData={{
+      nodes: [
+        {
+          id: 'custom_0',
+          type: 'custom',
+        },
+      ],
+    }}
+    materials={{
+      renderDefaultNode: NodeRender,
+      components: defaultFixedSemiMaterials,
+    }}
+  >
+    <EditorRenderer />
+  </FixedLayoutEditorProvider>
+);
+
+export default FlowGramApp;

+ 206 - 0
apps/docs/components/fixed-examples/step-3.tsx

@@ -0,0 +1,206 @@
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FC } from 'react';
+
+import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
+import {
+  FixedLayoutEditorProvider,
+  EditorRenderer,
+  FlowNodeEntity,
+  useNodeRender,
+  FlowNodeJSON,
+  FlowOperationService,
+  usePlayground,
+  useService,
+  FlowRendererKey,
+  useClientContext,
+} from '@flowgram.ai/fixed-layout-editor';
+
+export const NodeRender = ({ node }: { node: FlowNodeEntity }) => {
+  const {
+    onMouseEnter,
+    onMouseLeave,
+    startDrag,
+    form,
+    dragging,
+    isBlockOrderIcon,
+    isBlockIcon,
+    activated,
+  } = useNodeRender();
+  const ctx = useClientContext();
+
+  return (
+    <div
+      onMouseEnter={onMouseEnter}
+      onMouseLeave={onMouseLeave}
+      onMouseDown={(e) => {
+        startDrag(e);
+        e.stopPropagation();
+      }}
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: activated ? '#82a7fc' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+        opacity: dragging ? 0.3 : 1,
+        ...(isBlockOrderIcon || isBlockIcon ? { width: 260 } : {}),
+      }}
+    >
+      {form?.render()}
+      {/* 删除按钮 */}
+      <button
+        onClick={(e) => {
+          e.stopPropagation();
+          ctx.operation.deleteNode(node);
+        }}
+        style={{
+          position: 'absolute',
+          top: 4,
+          right: 4,
+          width: 20,
+          height: 20,
+          border: 'none',
+          borderRadius: '50%',
+          background: '#fff',
+          color: '#666',
+          fontSize: 12,
+          cursor: 'pointer',
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
+          transition: 'all 0.2s',
+        }}
+      >
+        ×
+      </button>
+    </div>
+  );
+};
+
+const useAddNode = () => {
+  const playground = usePlayground();
+  const flowOperationService = useService(FlowOperationService) as FlowOperationService;
+
+  const handleAdd = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
+    const blocks = addProps.blocks ? addProps.blocks : undefined;
+    const entity = flowOperationService.addFromNode(dropNode, {
+      ...addProps,
+      blocks,
+    });
+    setTimeout(() => {
+      playground.scrollToView({
+        bounds: entity.bounds,
+        scrollToCenter: true,
+      });
+    }, 10);
+    return entity;
+  };
+
+  const handleAddBranch = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
+    const index = dropNode.index + 1;
+    const entity = flowOperationService.addBlock(dropNode.originParent!, addProps, {
+      index,
+    });
+    return entity;
+  };
+
+  return {
+    handleAdd,
+    handleAddBranch,
+  };
+};
+
+const Adder: FC<{
+  from: FlowNodeEntity;
+  to?: FlowNodeEntity;
+  hoverActivated: boolean;
+}> = ({ from, hoverActivated }) => {
+  const playground = usePlayground();
+
+  const { handleAdd } = useAddNode();
+
+  if (playground.config.readonlyOrDisabled) return null;
+
+  return (
+    <div
+      style={{
+        width: hoverActivated ? 15 : 6,
+        height: hoverActivated ? 15 : 6,
+        backgroundColor: hoverActivated ? '#fff' : 'rgb(143, 149, 158)',
+        color: '#fff',
+        borderRadius: '50%',
+        display: 'flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+        cursor: 'pointer',
+      }}
+      onClick={() => {
+        handleAdd({ type: 'custom', id: `custom_${Date.now()}` }, from);
+      }}
+    >
+      {hoverActivated ? (
+        <span
+          style={{
+            color: '#3370ff',
+            fontSize: 12,
+          }}
+        >
+          +
+        </span>
+      ) : null}
+    </div>
+  );
+};
+
+const FlowGramApp = () => (
+  <FixedLayoutEditorProvider
+    nodeRegistries={[
+      {
+        type: 'custom',
+      },
+    ]}
+    initialData={{
+      nodes: [
+        {
+          id: 'start_0',
+          type: 'start',
+        },
+        {
+          id: 'custom_1',
+          type: 'custom',
+        },
+        {
+          id: 'end_2',
+          type: 'end',
+        },
+      ],
+    }}
+    onAllLayersRendered={(ctx) => {
+      setTimeout(() => {
+        ctx.playground.config.fitView(ctx.document.root.bounds.pad(30));
+      }, 10);
+    }}
+    materials={{
+      renderDefaultNode: NodeRender,
+      components: {
+        ...defaultFixedSemiMaterials,
+        [FlowRendererKey.ADDER]: Adder,
+      },
+    }}
+  >
+    <EditorRenderer />
+  </FixedLayoutEditorProvider>
+);
+
+export default FlowGramApp;

+ 212 - 0
apps/docs/components/fixed-examples/step-4.tsx

@@ -0,0 +1,212 @@
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FC } from 'react';
+
+import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
+import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
+import {
+  FixedLayoutEditorProvider,
+  EditorRenderer,
+  FlowNodeEntity,
+  useNodeRender,
+  FlowNodeJSON,
+  FlowOperationService,
+  usePlayground,
+  useService,
+  FlowRendererKey,
+  useClientContext,
+} from '@flowgram.ai/fixed-layout-editor';
+
+export const NodeRender = ({ node }: { node: FlowNodeEntity }) => {
+  const {
+    onMouseEnter,
+    onMouseLeave,
+    startDrag,
+    form,
+    dragging,
+    isBlockOrderIcon,
+    isBlockIcon,
+    activated,
+  } = useNodeRender();
+  const ctx = useClientContext();
+
+  return (
+    <div
+      onMouseEnter={onMouseEnter}
+      onMouseLeave={onMouseLeave}
+      onMouseDown={(e) => {
+        startDrag(e);
+        e.stopPropagation();
+      }}
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: activated ? '#82a7fc' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+        opacity: dragging ? 0.3 : 1,
+        ...(isBlockOrderIcon || isBlockIcon ? { width: 260 } : {}),
+      }}
+    >
+      {form?.render()}
+      {/* 删除按钮 */}
+      <button
+        onClick={(e) => {
+          e.stopPropagation();
+          ctx.operation.deleteNode(node);
+        }}
+        style={{
+          position: 'absolute',
+          top: 4,
+          right: 4,
+          width: 20,
+          height: 20,
+          border: 'none',
+          borderRadius: '50%',
+          background: '#fff',
+          color: '#666',
+          fontSize: 12,
+          cursor: 'pointer',
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
+          transition: 'all 0.2s',
+        }}
+      >
+        ×
+      </button>
+    </div>
+  );
+};
+
+const useAddNode = () => {
+  const playground = usePlayground();
+  const flowOperationService = useService(FlowOperationService) as FlowOperationService;
+
+  const handleAdd = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
+    const blocks = addProps.blocks ? addProps.blocks : undefined;
+    const entity = flowOperationService.addFromNode(dropNode, {
+      ...addProps,
+      blocks,
+    });
+    setTimeout(() => {
+      playground.scrollToView({
+        bounds: entity.bounds,
+        scrollToCenter: true,
+      });
+    }, 10);
+    return entity;
+  };
+
+  const handleAddBranch = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
+    const index = dropNode.index + 1;
+    const entity = flowOperationService.addBlock(dropNode.originParent!, addProps, {
+      index,
+    });
+    return entity;
+  };
+
+  return {
+    handleAdd,
+    handleAddBranch,
+  };
+};
+
+const Adder: FC<{
+  from: FlowNodeEntity;
+  to?: FlowNodeEntity;
+  hoverActivated: boolean;
+}> = ({ from, hoverActivated }) => {
+  const playground = usePlayground();
+
+  const { handleAdd } = useAddNode();
+
+  if (playground.config.readonlyOrDisabled) return null;
+
+  return (
+    <div
+      style={{
+        width: hoverActivated ? 15 : 6,
+        height: hoverActivated ? 15 : 6,
+        backgroundColor: hoverActivated ? '#fff' : 'rgb(143, 149, 158)',
+        color: '#fff',
+        borderRadius: '50%',
+        display: 'flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+        cursor: 'pointer',
+      }}
+      onClick={() => {
+        handleAdd({ type: 'custom', id: `custom_${Date.now()}` }, from);
+      }}
+    >
+      {hoverActivated ? (
+        <span
+          style={{
+            color: '#3370ff',
+            fontSize: 12,
+          }}
+        >
+          +
+        </span>
+      ) : null}
+    </div>
+  );
+};
+
+const FlowGramApp = () => (
+  <FixedLayoutEditorProvider
+    plugins={() => [
+      createMinimapPlugin({
+        enableDisplayAllNodes: true,
+      }),
+    ]}
+    nodeRegistries={[
+      {
+        type: 'custom',
+      },
+    ]}
+    initialData={{
+      nodes: [
+        {
+          id: 'start_0',
+          type: 'start',
+        },
+        {
+          id: 'custom_1',
+          type: 'custom',
+        },
+        {
+          id: 'end_2',
+          type: 'end',
+        },
+      ],
+    }}
+    onAllLayersRendered={(ctx) => {
+      setTimeout(() => {
+        ctx.playground.config.fitView(ctx.document.root.bounds.pad(30));
+      }, 10);
+    }}
+    materials={{
+      renderDefaultNode: NodeRender,
+      components: {
+        ...defaultFixedSemiMaterials,
+        [FlowRendererKey.ADDER]: Adder,
+      },
+    }}
+  >
+    <EditorRenderer />
+  </FixedLayoutEditorProvider>
+);
+
+export default FlowGramApp;

+ 91 - 0
apps/docs/components/fixed-examples/step-5/adder.tsx

@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FC } from 'react';
+
+import {
+  FlowNodeEntity,
+  FlowNodeJSON,
+  FlowOperationService,
+  usePlayground,
+  useService,
+} from '@flowgram.ai/fixed-layout-editor';
+
+const useAddNode = () => {
+  const playground = usePlayground();
+  const flowOperationService = useService(FlowOperationService) as FlowOperationService;
+
+  const handleAdd = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
+    const blocks = addProps.blocks ? addProps.blocks : undefined;
+    const entity = flowOperationService.addFromNode(dropNode, {
+      ...addProps,
+      blocks,
+    });
+    setTimeout(() => {
+      playground.scrollToView({
+        bounds: entity.bounds,
+        scrollToCenter: true,
+      });
+    }, 10);
+    return entity;
+  };
+
+  const handleAddBranch = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
+    const index = dropNode.index + 1;
+    const entity = flowOperationService.addBlock(dropNode.originParent!, addProps, {
+      index,
+    });
+    return entity;
+  };
+
+  return {
+    handleAdd,
+    handleAddBranch,
+  };
+};
+
+export const Adder: FC<{
+  from: FlowNodeEntity;
+  to?: FlowNodeEntity;
+  hoverActivated: boolean;
+}> = ({ from, hoverActivated }) => {
+  const playground = usePlayground();
+
+  const { handleAdd } = useAddNode();
+
+  if (playground.config.readonlyOrDisabled) return null;
+
+  return (
+    <div
+      style={{
+        width: hoverActivated ? 15 : 6,
+        height: hoverActivated ? 15 : 6,
+        backgroundColor: hoverActivated ? '#fff' : 'rgb(143, 149, 158)',
+        color: '#fff',
+        borderRadius: '50%',
+        display: 'flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+        cursor: 'pointer',
+      }}
+      onClick={() => {
+        handleAdd({ type: 'custom', id: `custom_${Date.now()}` }, from);
+      }}
+    >
+      {hoverActivated ? (
+        <span
+          style={{
+            color: '#3370ff',
+            fontSize: 12,
+          }}
+        >
+          +
+        </span>
+      ) : null}
+    </div>
+  );
+};

+ 21 - 0
apps/docs/components/fixed-examples/step-5/app.tsx

@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FixedLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/fixed-layout-editor';
+
+import { useEditorProps } from './use-editor-props';
+
+const FlowGramApp = () => {
+  const editorProps = useEditorProps();
+  return (
+    <FixedLayoutEditorProvider {...editorProps}>
+      <EditorRenderer />
+    </FixedLayoutEditorProvider>
+  );
+};
+
+export default FlowGramApp;

+ 23 - 0
apps/docs/components/fixed-examples/step-5/initial-data.ts

@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import type { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
+
+export const initialData: FlowDocumentJSON = {
+  nodes: [
+    {
+      id: 'start_0',
+      type: 'start',
+    },
+    {
+      id: 'custom_1',
+      type: 'custom',
+    },
+    {
+      id: 'end_2',
+      type: 'end',
+    },
+  ],
+};

+ 12 - 0
apps/docs/components/fixed-examples/step-5/node-registries.tsx

@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import type { FlowNodeMeta, FlowNodeRegistry } from '@flowgram.ai/fixed-layout-editor';
+
+export const nodeRegistries: FlowNodeRegistry<FlowNodeMeta>[] = [
+  {
+    type: 'custom',
+  },
+];

+ 80 - 0
apps/docs/components/fixed-examples/step-5/node-render.tsx

@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FlowNodeEntity, useNodeRender, useClientContext } from '@flowgram.ai/fixed-layout-editor';
+
+export const NodeRender = ({ node }: { node: FlowNodeEntity }) => {
+  const {
+    onMouseEnter,
+    onMouseLeave,
+    startDrag,
+    form,
+    dragging,
+    isBlockOrderIcon,
+    isBlockIcon,
+    activated,
+  } = useNodeRender();
+  const ctx = useClientContext();
+
+  return (
+    <div
+      onMouseEnter={onMouseEnter}
+      onMouseLeave={onMouseLeave}
+      onMouseDown={(e) => {
+        startDrag(e);
+        e.stopPropagation();
+      }}
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: activated ? '#82a7fc' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+        opacity: dragging ? 0.3 : 1,
+        ...(isBlockOrderIcon || isBlockIcon ? { width: 260 } : {}),
+      }}
+    >
+      {form?.render()}
+      {/* 删除按钮 */}
+      <button
+        onClick={(e) => {
+          e.stopPropagation();
+          ctx.operation.deleteNode(node);
+        }}
+        style={{
+          position: 'absolute',
+          top: 4,
+          right: 4,
+          width: 20,
+          height: 20,
+          border: 'none',
+          borderRadius: '50%',
+          background: '#fff',
+          color: '#666',
+          fontSize: 12,
+          cursor: 'pointer',
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
+          transition: 'all 0.2s',
+        }}
+      >
+        ×
+      </button>
+    </div>
+  );
+};

+ 39 - 0
apps/docs/components/fixed-examples/step-5/use-editor-props.tsx

@@ -0,0 +1,39 @@
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { useMemo } from 'react';
+
+import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
+import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
+import { FlowRendererKey, FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor';
+
+import { NodeRender } from './node-render';
+import { nodeRegistries } from './node-registries';
+import { initialData } from './initial-data';
+import { Adder } from './adder';
+
+export function useEditorProps(): FixedLayoutProps {
+  return useMemo<FixedLayoutProps>(
+    () => ({
+      plugins: () => [
+        createMinimapPlugin({
+          enableDisplayAllNodes: true,
+        }),
+      ],
+      nodeRegistries,
+      initialData,
+      onAllLayersRendered: (ctx) => {
+        setTimeout(() => {
+          ctx.playground.config.fitView(ctx.document.root.bounds.pad(30));
+        }, 10);
+      },
+      materials: {
+        renderDefaultNode: NodeRender,
+        components: {
+          ...defaultFixedSemiMaterials,
+          [FlowRendererKey.ADDER]: Adder,
+        },
+      },
+    }),
+    []
+  );
+}

+ 101 - 0
apps/docs/components/fixed-examples/step-6/adder.tsx

@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FC } from 'react';
+
+import {
+  FlowNodeEntity,
+  FlowNodeJSON,
+  FlowOperationService,
+  usePlayground,
+  useService,
+} from '@flowgram.ai/fixed-layout-editor';
+
+const useAddNode = () => {
+  const playground = usePlayground();
+  const flowOperationService = useService(FlowOperationService) as FlowOperationService;
+
+  const handleAdd = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
+    const blocks = addProps.blocks ? addProps.blocks : undefined;
+    const entity = flowOperationService.addFromNode(dropNode, {
+      ...addProps,
+      blocks,
+    });
+    setTimeout(() => {
+      playground.scrollToView({
+        bounds: entity.bounds,
+        scrollToCenter: true,
+      });
+    }, 10);
+    return entity;
+  };
+
+  const handleAddBranch = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
+    const index = dropNode.index + 1;
+    const entity = flowOperationService.addBlock(dropNode.originParent!, addProps, {
+      index,
+    });
+    return entity;
+  };
+
+  return {
+    handleAdd,
+    handleAddBranch,
+  };
+};
+
+export const Adder: FC<{
+  from: FlowNodeEntity;
+  to?: FlowNodeEntity;
+  hoverActivated: boolean;
+}> = ({ from, hoverActivated }) => {
+  const playground = usePlayground();
+
+  const { handleAdd } = useAddNode();
+
+  if (playground.config.readonlyOrDisabled) return null;
+
+  return (
+    <div
+      style={{
+        width: hoverActivated ? 15 : 6,
+        height: hoverActivated ? 15 : 6,
+        backgroundColor: hoverActivated ? '#fff' : 'rgb(143, 149, 158)',
+        color: '#fff',
+        borderRadius: '50%',
+        display: 'flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+        cursor: 'pointer',
+      }}
+      onClick={() => {
+        handleAdd(
+          {
+            type: 'custom',
+            id: `custom_${Date.now()}`,
+            data: {
+              title: 'New Custom Node',
+              content: 'Custom Node Content',
+            },
+          },
+          from
+        );
+      }}
+    >
+      {hoverActivated ? (
+        <span
+          style={{
+            color: '#3370ff',
+            fontSize: 12,
+          }}
+        >
+          +
+        </span>
+      ) : null}
+    </div>
+  );
+};

+ 21 - 0
apps/docs/components/fixed-examples/step-6/app.tsx

@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FixedLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/fixed-layout-editor';
+
+import { useEditorProps } from './use-editor-props';
+
+const FlowGramApp = () => {
+  const editorProps = useEditorProps();
+  return (
+    <FixedLayoutEditorProvider {...editorProps}>
+      <EditorRenderer />
+    </FixedLayoutEditorProvider>
+  );
+};
+
+export default FlowGramApp;

+ 81 - 0
apps/docs/components/fixed-examples/step-6/initial-data.ts

@@ -0,0 +1,81 @@
+import type { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
+
+export const initialData: FlowDocumentJSON = {
+  nodes: [
+    // 开始节点
+    {
+      id: 'start_0',
+      type: 'start',
+      data: {
+        title: 'Start',
+        content: 'start content',
+      },
+      blocks: [],
+    },
+    // 分支节点
+    {
+      id: 'condition_0',
+      type: 'condition',
+      data: {
+        title: 'Condition',
+        content: 'condition content',
+      },
+      blocks: [
+        {
+          id: 'branch_0',
+          type: 'block',
+          data: {
+            title: 'Branch 0',
+            content: 'branch 1 content',
+          },
+          blocks: [
+            {
+              id: 'custom_0',
+              type: 'custom',
+              data: {
+                title: 'Custom',
+                content: 'custom content',
+              },
+            },
+          ],
+        },
+        {
+          id: 'branch_1',
+          type: 'block',
+          data: {
+            title: 'Branch 1',
+            content: 'branch 1 content',
+          },
+          blocks: [
+            {
+              id: 'break_0',
+              type: 'break',
+              data: {
+                title: 'Break',
+                content: 'Break content',
+              },
+            },
+          ],
+        },
+        {
+          id: 'branch_2',
+          type: 'block',
+          data: {
+            title: 'Branch 2',
+            content: 'branch 2 content',
+          },
+          blocks: [],
+        },
+      ],
+    },
+    // 结束节点
+    {
+      id: 'end_0',
+      type: 'end',
+      data: {
+        title: 'End',
+        content: 'end content',
+      },
+    },
+  ],
+};

+ 79 - 0
apps/docs/components/fixed-examples/step-6/node-registries.tsx

@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 202 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import type { FlowNodeMeta, FlowNodeRegistry } from '@flowgram.ai/fixed-layout-editor';
+
+const randomID = () => Math.random().toString(36).slice(2, 7);
+
+export const nodeRegistries: FlowNodeRegistry<FlowNodeMeta>[] = [
+  {
+    /**
+     * 自定义节点类型
+     */
+    type: 'condition',
+    /**
+     * 自定义节点扩展:
+     *  - loop: 扩展为循环节点
+     *  - start: 扩展为开始节点
+     *  - dynamicSplit: 扩展为分支节点
+     *  - end: 扩展为结束节点
+     *  - tryCatch: 扩展为 tryCatch 节点
+     *  - break: 分支断开
+     *  - default: 扩展为普通节点 (默认)
+     */
+    extend: 'dynamicSplit',
+    /**
+     * 节点配置信息
+     */
+    meta: {
+      // isStart: false, // 是否为开始节点
+      // isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点
+      // draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽
+      // selectable: false, // 触发器等开始节点不能被框选
+      // deleteDisable: true, // 禁止删除
+      // copyDisable: true, // 禁止copy
+      // addDisable: true, // 禁止添加
+    },
+    onAdd() {
+      return {
+        id: `condition_${randomID()}`,
+        type: 'condition',
+        data: {
+          title: 'Condition',
+        },
+        blocks: [
+          {
+            id: randomID(),
+            type: 'block',
+            data: {
+              title: 'If_0',
+            },
+          },
+          {
+            id: randomID(),
+            type: 'block',
+            data: {
+              title: 'If_1',
+            },
+          },
+        ],
+      };
+    },
+  },
+  {
+    type: 'custom',
+    meta: {},
+    onAdd() {
+      return {
+        id: `custom_${randomID()}`,
+        type: 'custom',
+        data: {
+          title: 'Custom',
+          content: 'this is custom content',
+        },
+      };
+    },
+  },
+];

+ 80 - 0
apps/docs/components/fixed-examples/step-6/node-render.tsx

@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FlowNodeEntity, useNodeRender, useClientContext } from '@flowgram.ai/fixed-layout-editor';
+
+export const NodeRender = ({ node }: { node: FlowNodeEntity }) => {
+  const {
+    onMouseEnter,
+    onMouseLeave,
+    startDrag,
+    form,
+    dragging,
+    isBlockOrderIcon,
+    isBlockIcon,
+    activated,
+  } = useNodeRender();
+  const ctx = useClientContext();
+
+  return (
+    <div
+      onMouseEnter={onMouseEnter}
+      onMouseLeave={onMouseLeave}
+      onMouseDown={(e) => {
+        startDrag(e);
+        e.stopPropagation();
+      }}
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: activated ? '#82a7fc' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+        opacity: dragging ? 0.3 : 1,
+        ...(isBlockOrderIcon || isBlockIcon ? { width: 260 } : {}),
+      }}
+    >
+      {form?.render()}
+      {/* 删除按钮 */}
+      <button
+        onClick={(e) => {
+          e.stopPropagation();
+          ctx.operation.deleteNode(node);
+        }}
+        style={{
+          position: 'absolute',
+          top: 4,
+          right: 4,
+          width: 20,
+          height: 20,
+          border: 'none',
+          borderRadius: '50%',
+          background: '#fff',
+          color: '#666',
+          fontSize: 12,
+          cursor: 'pointer',
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
+          transition: 'all 0.2s',
+        }}
+      >
+        ×
+      </button>
+    </div>
+  );
+};

+ 81 - 0
apps/docs/components/fixed-examples/step-6/use-editor-props.tsx

@@ -0,0 +1,81 @@
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { useMemo } from 'react';
+
+import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
+import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
+import { FlowRendererKey, FixedLayoutProps, Field } from '@flowgram.ai/fixed-layout-editor';
+
+import { NodeRender } from './node-render';
+import { nodeRegistries } from './node-registries';
+import { initialData } from './initial-data';
+import { Adder } from './adder';
+
+export function useEditorProps(): FixedLayoutProps {
+  return useMemo<FixedLayoutProps>(
+    () => ({
+      plugins: () => [
+        createMinimapPlugin({
+          enableDisplayAllNodes: true,
+        }),
+      ],
+      nodeRegistries,
+      initialData,
+      materials: {
+        renderDefaultNode: NodeRender,
+        components: {
+          ...defaultFixedSemiMaterials,
+          [FlowRendererKey.ADDER]: Adder,
+        },
+      },
+      onAllLayersRendered: (ctx) => {
+        setTimeout(() => {
+          ctx.playground.config.fitView(ctx.document.root.bounds.pad(30));
+        }, 10);
+      },
+      /**
+       * Get the default node registry, which will be merged with the 'nodeRegistries'
+       * 提供默认的节点注册,这个会和 nodeRegistries 做合并
+       */
+      getNodeDefaultRegistry(type) {
+        return {
+          type,
+          meta: {
+            defaultExpanded: true,
+          },
+          formMeta: {
+            /**
+             * Render form
+             */
+            render: () => (
+              <>
+                <Field<string> name="title">{({ field }) => <div>{field.value}</div>}</Field>
+                <Field<string> name="content">
+                  <input />
+                </Field>
+              </>
+            ),
+          },
+        };
+      },
+      /**
+       * Redo/Undo enable
+       */
+      history: {
+        enable: true,
+        enableChangeNode: true, // Listen Node engine data change
+        onApply: (ctx) => {
+          if (ctx.document.disposed) return;
+          // Listen change to trigger auto save
+          console.log('auto save: ', ctx.document.toJSON());
+        },
+      },
+      /**
+       * Node engine enable, you can configure formMeta in the FlowNodeRegistry
+       */ nodeEngine: {
+        enable: true,
+      },
+    }),
+    []
+  );
+}

+ 101 - 0
apps/docs/components/fixed-examples/step-7/adder.tsx

@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FC } from 'react';
+
+import {
+  FlowNodeEntity,
+  FlowNodeJSON,
+  FlowOperationService,
+  usePlayground,
+  useService,
+} from '@flowgram.ai/fixed-layout-editor';
+
+const useAddNode = () => {
+  const playground = usePlayground();
+  const flowOperationService = useService(FlowOperationService) as FlowOperationService;
+
+  const handleAdd = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
+    const blocks = addProps.blocks ? addProps.blocks : undefined;
+    const entity = flowOperationService.addFromNode(dropNode, {
+      ...addProps,
+      blocks,
+    });
+    setTimeout(() => {
+      playground.scrollToView({
+        bounds: entity.bounds,
+        scrollToCenter: true,
+      });
+    }, 10);
+    return entity;
+  };
+
+  const handleAddBranch = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
+    const index = dropNode.index + 1;
+    const entity = flowOperationService.addBlock(dropNode.originParent!, addProps, {
+      index,
+    });
+    return entity;
+  };
+
+  return {
+    handleAdd,
+    handleAddBranch,
+  };
+};
+
+export const Adder: FC<{
+  from: FlowNodeEntity;
+  to?: FlowNodeEntity;
+  hoverActivated: boolean;
+}> = ({ from, hoverActivated }) => {
+  const playground = usePlayground();
+
+  const { handleAdd } = useAddNode();
+
+  if (playground.config.readonlyOrDisabled) return null;
+
+  return (
+    <div
+      style={{
+        width: hoverActivated ? 15 : 6,
+        height: hoverActivated ? 15 : 6,
+        backgroundColor: hoverActivated ? '#fff' : 'rgb(143, 149, 158)',
+        color: '#fff',
+        borderRadius: '50%',
+        display: 'flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+        cursor: 'pointer',
+      }}
+      onClick={() => {
+        handleAdd(
+          {
+            type: 'custom',
+            id: `custom_${Date.now()}`,
+            data: {
+              title: 'New Custom Node',
+              content: 'Custom Node Content',
+            },
+          },
+          from
+        );
+      }}
+    >
+      {hoverActivated ? (
+        <span
+          style={{
+            color: '#3370ff',
+            fontSize: 12,
+          }}
+        >
+          +
+        </span>
+      ) : null}
+    </div>
+  );
+};

+ 25 - 0
apps/docs/components/fixed-examples/step-7/app.tsx

@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FixedLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/fixed-layout-editor';
+
+import { useEditorProps } from './use-editor-props';
+import { Tools } from './tools';
+import { Minimap } from './minimap';
+
+const FlowGramApp = () => {
+  const editorProps = useEditorProps();
+  return (
+    <FixedLayoutEditorProvider {...editorProps}>
+      <EditorRenderer />
+      <Tools />
+      <Minimap />
+    </FixedLayoutEditorProvider>
+  );
+};
+
+export default FlowGramApp;

+ 81 - 0
apps/docs/components/fixed-examples/step-7/initial-data.ts

@@ -0,0 +1,81 @@
+import type { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
+
+export const initialData: FlowDocumentJSON = {
+  nodes: [
+    // 开始节点
+    {
+      id: 'start_0',
+      type: 'start',
+      data: {
+        title: 'Start',
+        content: 'start content',
+      },
+      blocks: [],
+    },
+    // 分支节点
+    {
+      id: 'condition_0',
+      type: 'condition',
+      data: {
+        title: 'Condition',
+        content: 'condition content',
+      },
+      blocks: [
+        {
+          id: 'branch_0',
+          type: 'block',
+          data: {
+            title: 'Branch 0',
+            content: 'branch 1 content',
+          },
+          blocks: [
+            {
+              id: 'custom_0',
+              type: 'custom',
+              data: {
+                title: 'Custom',
+                content: 'custom content',
+              },
+            },
+          ],
+        },
+        {
+          id: 'branch_1',
+          type: 'block',
+          data: {
+            title: 'Branch 1',
+            content: 'branch 1 content',
+          },
+          blocks: [
+            {
+              id: 'break_0',
+              type: 'break',
+              data: {
+                title: 'Break',
+                content: 'Break content',
+              },
+            },
+          ],
+        },
+        {
+          id: 'branch_2',
+          type: 'block',
+          data: {
+            title: 'Branch 2',
+            content: 'branch 2 content',
+          },
+          blocks: [],
+        },
+      ],
+    },
+    // 结束节点
+    {
+      id: 'end_0',
+      type: 'end',
+      data: {
+        title: 'End',
+        content: 'end content',
+      },
+    },
+  ],
+};

+ 35 - 0
apps/docs/components/fixed-examples/step-7/minimap.tsx

@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { MinimapRender } from '@flowgram.ai/minimap-plugin';
+
+export const Minimap = () => (
+  <div
+    style={{
+      position: 'absolute',
+      left: 16,
+      bottom: 72,
+      zIndex: 100,
+      width: 118,
+    }}
+  >
+    <MinimapRender
+      containerStyles={{
+        pointerEvents: 'auto',
+        position: 'relative',
+        top: 'unset',
+        right: 'unset',
+        bottom: 'unset',
+        left: 'unset',
+      }}
+      inactiveStyle={{
+        opacity: 1,
+        scale: 1,
+        translateX: 0,
+        translateY: 0,
+      }}
+    />
+  </div>
+);

+ 79 - 0
apps/docs/components/fixed-examples/step-7/node-registries.tsx

@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 202 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import type { FlowNodeMeta, FlowNodeRegistry } from '@flowgram.ai/fixed-layout-editor';
+
+const randomID = () => Math.random().toString(36).slice(2, 7);
+
+export const nodeRegistries: FlowNodeRegistry<FlowNodeMeta>[] = [
+  {
+    /**
+     * 自定义节点类型
+     */
+    type: 'condition',
+    /**
+     * 自定义节点扩展:
+     *  - loop: 扩展为循环节点
+     *  - start: 扩展为开始节点
+     *  - dynamicSplit: 扩展为分支节点
+     *  - end: 扩展为结束节点
+     *  - tryCatch: 扩展为 tryCatch 节点
+     *  - break: 分支断开
+     *  - default: 扩展为普通节点 (默认)
+     */
+    extend: 'dynamicSplit',
+    /**
+     * 节点配置信息
+     */
+    meta: {
+      // isStart: false, // 是否为开始节点
+      // isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点
+      // draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽
+      // selectable: false, // 触发器等开始节点不能被框选
+      // deleteDisable: true, // 禁止删除
+      // copyDisable: true, // 禁止copy
+      // addDisable: true, // 禁止添加
+    },
+    onAdd() {
+      return {
+        id: `condition_${randomID()}`,
+        type: 'condition',
+        data: {
+          title: 'Condition',
+        },
+        blocks: [
+          {
+            id: randomID(),
+            type: 'block',
+            data: {
+              title: 'If_0',
+            },
+          },
+          {
+            id: randomID(),
+            type: 'block',
+            data: {
+              title: 'If_1',
+            },
+          },
+        ],
+      };
+    },
+  },
+  {
+    type: 'custom',
+    meta: {},
+    onAdd() {
+      return {
+        id: `custom_${randomID()}`,
+        type: 'custom',
+        data: {
+          title: 'Custom',
+          content: 'this is custom content',
+        },
+      };
+    },
+  },
+];

+ 80 - 0
apps/docs/components/fixed-examples/step-7/node-render.tsx

@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { FlowNodeEntity, useNodeRender, useClientContext } from '@flowgram.ai/fixed-layout-editor';
+
+export const NodeRender = ({ node }: { node: FlowNodeEntity }) => {
+  const {
+    onMouseEnter,
+    onMouseLeave,
+    startDrag,
+    form,
+    dragging,
+    isBlockOrderIcon,
+    isBlockIcon,
+    activated,
+  } = useNodeRender();
+  const ctx = useClientContext();
+
+  return (
+    <div
+      onMouseEnter={onMouseEnter}
+      onMouseLeave={onMouseLeave}
+      onMouseDown={(e) => {
+        startDrag(e);
+        e.stopPropagation();
+      }}
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: activated ? '#82a7fc' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+        opacity: dragging ? 0.3 : 1,
+        ...(isBlockOrderIcon || isBlockIcon ? { width: 260 } : {}),
+      }}
+    >
+      {form?.render()}
+      {/* 删除按钮 */}
+      <button
+        onClick={(e) => {
+          e.stopPropagation();
+          ctx.operation.deleteNode(node);
+        }}
+        style={{
+          position: 'absolute',
+          top: 4,
+          right: 4,
+          width: 20,
+          height: 20,
+          border: 'none',
+          borderRadius: '50%',
+          background: '#fff',
+          color: '#666',
+          fontSize: 12,
+          cursor: 'pointer',
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
+          transition: 'all 0.2s',
+        }}
+      >
+        ×
+      </button>
+    </div>
+  );
+};

+ 85 - 0
apps/docs/components/fixed-examples/step-7/tools.tsx

@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { CSSProperties, useEffect, useState } from 'react';
+
+import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
+
+export const Tools = () => {
+  const { history } = useClientContext();
+  const tools = usePlaygroundTools();
+  const [canUndo, setCanUndo] = useState(false);
+  const [canRedo, setCanRedo] = useState(false);
+
+  const buttonStyle: CSSProperties = {
+    border: '1px solid #e0e0e0',
+    borderRadius: '4px',
+    cursor: 'pointer',
+    padding: '4px',
+    color: '#141414',
+    background: '#e1e3e4',
+  };
+
+  useEffect(() => {
+    const disposable = history.undoRedoService.onChange(() => {
+      setCanUndo(history.canUndo());
+      setCanRedo(history.canRedo());
+    });
+    return () => disposable.dispose();
+  }, [history]);
+
+  return (
+    <div
+      style={{ position: 'absolute', zIndex: 10, bottom: 34, left: 16, display: 'flex', gap: 8 }}
+    >
+      <button style={buttonStyle} onClick={() => tools.zoomin()}>
+        ZoomIn
+      </button>
+      <button style={buttonStyle} onClick={() => tools.zoomout()}>
+        ZoomOut
+      </button>
+      <span
+        style={{
+          ...buttonStyle,
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          cursor: 'default',
+          width: 40,
+        }}
+      >
+        {Math.floor(tools.zoom * 100)}%
+      </span>
+      <button style={buttonStyle} onClick={() => tools.fitView()}>
+        FitView
+      </button>
+      <button style={buttonStyle} onClick={() => tools.changeLayout()}>
+        ChangeLayout
+      </button>
+      <button
+        style={{
+          ...buttonStyle,
+          cursor: canUndo ? 'pointer' : 'not-allowed',
+          color: canUndo ? '#141414' : '#b1b1b1',
+        }}
+        onClick={() => history.undo()}
+        disabled={!canUndo}
+      >
+        Undo
+      </button>
+      <button
+        style={{
+          ...buttonStyle,
+          cursor: canRedo ? 'pointer' : 'not-allowed',
+          color: canRedo ? '#141414' : '#b1b1b1',
+        }}
+        onClick={() => history.redo()}
+        disabled={!canRedo}
+      >
+        Redo
+      </button>
+    </div>
+  );
+};

+ 87 - 0
apps/docs/components/fixed-examples/step-7/use-editor-props.tsx

@@ -0,0 +1,87 @@
+import '@flowgram.ai/fixed-layout-editor/index.css';
+
+import { useMemo } from 'react';
+
+import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
+import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
+import { FlowRendererKey, FixedLayoutProps, Field } from '@flowgram.ai/fixed-layout-editor';
+
+import { NodeRender } from './node-render';
+import { nodeRegistries } from './node-registries';
+import { initialData } from './initial-data';
+import { Adder } from './adder';
+
+export function useEditorProps(): FixedLayoutProps {
+  return useMemo<FixedLayoutProps>(
+    () => ({
+      plugins: () => [
+        createMinimapPlugin({
+          disableLayer: true,
+          enableDisplayAllNodes: true,
+          canvasStyle: {
+            canvasWidth: 100,
+            canvasHeight: 50,
+            canvasPadding: 50,
+          },
+        }),
+      ],
+      nodeRegistries,
+      initialData,
+      materials: {
+        renderDefaultNode: NodeRender,
+        components: {
+          ...defaultFixedSemiMaterials,
+          [FlowRendererKey.ADDER]: Adder,
+        },
+      },
+      onAllLayersRendered: (ctx) => {
+        setTimeout(() => {
+          ctx.playground.config.fitView(ctx.document.root.bounds.pad(30));
+        }, 10);
+      },
+      /**
+       * Get the default node registry, which will be merged with the 'nodeRegistries'
+       * 提供默认的节点注册,这个会和 nodeRegistries 做合并
+       */
+      getNodeDefaultRegistry(type) {
+        return {
+          type,
+          meta: {
+            defaultExpanded: true,
+          },
+          formMeta: {
+            /**
+             * Render form
+             */
+            render: () => (
+              <>
+                <Field<string> name="title">{({ field }) => <div>{field.value}</div>}</Field>
+                <Field<string> name="content">
+                  <input />
+                </Field>
+              </>
+            ),
+          },
+        };
+      },
+      /**
+       * Redo/Undo enable
+       */
+      history: {
+        enable: true,
+        enableChangeNode: true, // Listen Node engine data change
+        onApply: (ctx) => {
+          if (ctx.document.disposed) return;
+          // Listen change to trigger auto save
+          console.log('auto save: ', ctx.document.toJSON());
+        },
+      },
+      /**
+       * Node engine enable, you can configure formMeta in the FlowNodeRegistry
+       */ nodeEngine: {
+        enable: true,
+      },
+    }),
+    []
+  );
+}

+ 10 - 0
apps/docs/components/free-examples/step-1.tsx

@@ -0,0 +1,10 @@
+import '@flowgram.ai/free-layout-editor/index.css';
+import { FreeLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/free-layout-editor';
+
+const FlowGramApp = () => (
+  <FreeLayoutEditorProvider>
+    <EditorRenderer />
+  </FreeLayoutEditorProvider>
+);
+
+export default FlowGramApp;

+ 65 - 0
apps/docs/components/free-examples/step-2.tsx

@@ -0,0 +1,65 @@
+import '@flowgram.ai/free-layout-editor/index.css';
+
+import {
+  FreeLayoutEditorProvider,
+  EditorRenderer,
+  useNodeRender,
+  WorkflowNodeProps,
+  WorkflowNodeRenderer,
+} from '@flowgram.ai/free-layout-editor';
+
+const NodeRender = (props: WorkflowNodeProps) => {
+  const { form, selected } = useNodeRender();
+  return (
+    <WorkflowNodeRenderer
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+      }}
+      node={props.node}
+    >
+      {form?.render()}
+    </WorkflowNodeRenderer>
+  );
+};
+
+const FlowGramApp = () => (
+  <FreeLayoutEditorProvider
+    materials={{
+      renderDefaultNode: NodeRender,
+    }}
+    nodeRegistries={[
+      {
+        type: 'custom',
+      },
+    ]}
+    initialData={{
+      nodes: [
+        {
+          id: '1',
+          type: 'custom',
+          meta: {
+            position: { x: 250, y: 100 },
+          },
+        },
+      ],
+      edges: [],
+    }}
+  >
+    <EditorRenderer />
+  </FreeLayoutEditorProvider>
+);
+
+export default FlowGramApp;

+ 82 - 0
apps/docs/components/free-examples/step-3.tsx

@@ -0,0 +1,82 @@
+import '@flowgram.ai/free-layout-editor/index.css';
+
+import {
+  FreeLayoutEditorProvider,
+  EditorRenderer,
+  useNodeRender,
+  WorkflowNodeProps,
+  WorkflowNodeRenderer,
+} from '@flowgram.ai/free-layout-editor';
+
+const NodeRender = (props: WorkflowNodeProps) => {
+  const { form, selected } = useNodeRender();
+  return (
+    <WorkflowNodeRenderer
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+      }}
+      node={props.node}
+    >
+      {form?.render()}
+    </WorkflowNodeRenderer>
+  );
+};
+
+const FlowGramApp = () => (
+  <FreeLayoutEditorProvider
+    onAllLayersRendered={(ctx) => {
+      ctx.tools.fitView(false);
+    }}
+    materials={{
+      renderDefaultNode: NodeRender,
+    }}
+    nodeRegistries={[
+      {
+        type: 'custom',
+      },
+    ]}
+    canDeleteNode={() => true}
+    canDeleteLine={() => true}
+    initialData={{
+      nodes: [
+        {
+          id: '1',
+          type: 'custom',
+          meta: {
+            position: { x: 0, y: 0 },
+          },
+        },
+        {
+          id: '2',
+          type: 'custom',
+          meta: {
+            position: { x: 400, y: 0 },
+          },
+        },
+      ],
+      edges: [
+        {
+          sourceNodeID: '1',
+          targetNodeID: '2',
+        },
+      ],
+    }}
+  >
+    <EditorRenderer />
+  </FreeLayoutEditorProvider>
+);
+
+export default FlowGramApp;

+ 126 - 0
apps/docs/components/free-examples/step-4.tsx

@@ -0,0 +1,126 @@
+import '@flowgram.ai/free-layout-editor/index.css';
+
+import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
+import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
+import {
+  FreeLayoutEditorProvider,
+  EditorRenderer,
+  useNodeRender,
+  WorkflowNodeProps,
+  WorkflowNodeRenderer,
+} from '@flowgram.ai/free-layout-editor';
+
+const NodeRender = (props: WorkflowNodeProps) => {
+  const { form, selected } = useNodeRender();
+  return (
+    <WorkflowNodeRenderer
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+      }}
+      node={props.node}
+    >
+      {form?.render()}
+    </WorkflowNodeRenderer>
+  );
+};
+
+const FlowGramApp = () => (
+  <FreeLayoutEditorProvider
+    plugins={() => [createMinimapPlugin({}), createFreeSnapPlugin({})]}
+    onAllLayersRendered={(ctx) => {
+      ctx.tools.fitView(false);
+    }}
+    materials={{
+      renderDefaultNode: NodeRender,
+    }}
+    nodeRegistries={[
+      {
+        type: 'custom',
+      },
+    ]}
+    canDeleteNode={() => true}
+    canDeleteLine={() => true}
+    initialData={{
+      nodes: [
+        {
+          id: '1',
+          type: 'custom',
+          meta: {
+            position: { x: 0, y: 0 },
+          },
+        },
+        {
+          id: '2',
+          type: 'custom',
+          meta: {
+            position: { x: 400, y: -200 },
+          },
+        },
+        {
+          id: '3',
+          type: 'custom',
+          meta: {
+            position: { x: 400, y: 0 },
+          },
+        },
+        {
+          id: '4',
+          type: 'custom',
+          meta: {
+            position: { x: 400, y: 200 },
+          },
+        },
+        {
+          id: '5',
+          type: 'custom',
+          meta: {
+            position: { x: 800, y: 0 },
+          },
+        },
+      ],
+      edges: [
+        {
+          sourceNodeID: '1',
+          targetNodeID: '2',
+        },
+        {
+          sourceNodeID: '1',
+          targetNodeID: '3',
+        },
+        {
+          sourceNodeID: '1',
+          targetNodeID: '4',
+        },
+        {
+          sourceNodeID: '2',
+          targetNodeID: '5',
+        },
+        {
+          sourceNodeID: '3',
+          targetNodeID: '5',
+        },
+        {
+          sourceNodeID: '4',
+          targetNodeID: '5',
+        },
+      ],
+    }}
+  >
+    <EditorRenderer />
+  </FreeLayoutEditorProvider>
+);
+
+export default FlowGramApp;

+ 16 - 0
apps/docs/components/free-examples/step-5/app.tsx

@@ -0,0 +1,16 @@
+import '@flowgram.ai/free-layout-editor/index.css';
+
+import { FreeLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/free-layout-editor';
+
+import { useEditorProps } from './use-editor-props';
+
+const FlowGramApp = () => {
+  const editorProps = useEditorProps();
+  return (
+    <FreeLayoutEditorProvider {...editorProps}>
+      <EditorRenderer />
+    </FreeLayoutEditorProvider>
+  );
+};
+
+export default FlowGramApp;

+ 67 - 0
apps/docs/components/free-examples/step-5/initial-data.ts

@@ -0,0 +1,67 @@
+import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
+
+export const initialData: WorkflowJSON = {
+  nodes: [
+    {
+      id: '1',
+      type: 'custom',
+      meta: {
+        position: { x: 0, y: 0 },
+      },
+    },
+    {
+      id: '2',
+      type: 'custom',
+      meta: {
+        position: { x: 400, y: -200 },
+      },
+    },
+    {
+      id: '3',
+      type: 'custom',
+      meta: {
+        position: { x: 400, y: 0 },
+      },
+    },
+    {
+      id: '4',
+      type: 'custom',
+      meta: {
+        position: { x: 400, y: 200 },
+      },
+    },
+    {
+      id: '5',
+      type: 'custom',
+      meta: {
+        position: { x: 800, y: 0 },
+      },
+    },
+  ],
+  edges: [
+    {
+      sourceNodeID: '1',
+      targetNodeID: '2',
+    },
+    {
+      sourceNodeID: '1',
+      targetNodeID: '3',
+    },
+    {
+      sourceNodeID: '1',
+      targetNodeID: '4',
+    },
+    {
+      sourceNodeID: '2',
+      targetNodeID: '5',
+    },
+    {
+      sourceNodeID: '3',
+      targetNodeID: '5',
+    },
+    {
+      sourceNodeID: '4',
+      targetNodeID: '5',
+    },
+  ],
+};

+ 11 - 0
apps/docs/components/free-examples/step-5/node-registries.tsx

@@ -0,0 +1,11 @@
+import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
+
+/**
+ * You can customize your own node registry
+ * 你可以自定义节点的注册器
+ */
+export const nodeRegistries: WorkflowNodeRegistry[] = [
+  {
+    type: 'custom',
+  },
+];

+ 34 - 0
apps/docs/components/free-examples/step-5/node-render.tsx

@@ -0,0 +1,34 @@
+import '@flowgram.ai/free-layout-editor/index.css';
+
+import {
+  useNodeRender,
+  WorkflowNodeProps,
+  WorkflowNodeRenderer,
+} from '@flowgram.ai/free-layout-editor';
+
+export const NodeRender = (props: WorkflowNodeProps) => {
+  const { form, selected } = useNodeRender();
+  return (
+    <WorkflowNodeRenderer
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+      }}
+      node={props.node}
+    >
+      {form?.render()}
+    </WorkflowNodeRenderer>
+  );
+};

+ 27 - 0
apps/docs/components/free-examples/step-5/use-editor-props.tsx

@@ -0,0 +1,27 @@
+import { useMemo } from 'react';
+
+import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
+import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
+import { FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
+
+import { NodeRender } from './node-render';
+import { nodeRegistries } from './node-registries';
+import { initialData } from './initial-data';
+
+export const useEditorProps = () =>
+  useMemo<FreeLayoutProps>(
+    () => ({
+      plugins: () => [createMinimapPlugin({}), createFreeSnapPlugin({})],
+      onAllLayersRendered: (ctx) => {
+        ctx.tools.fitView(false);
+      },
+      materials: {
+        renderDefaultNode: NodeRender,
+      },
+      nodeRegistries,
+      canDeleteNode: () => true,
+      canDeleteLine: () => true,
+      initialData,
+    }),
+    []
+  );

+ 16 - 0
apps/docs/components/free-examples/step-6/app.tsx

@@ -0,0 +1,16 @@
+import '@flowgram.ai/free-layout-editor/index.css';
+
+import { FreeLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/free-layout-editor';
+
+import { useEditorProps } from './use-editor-props';
+
+const FlowGramApp = () => {
+  const editorProps = useEditorProps();
+  return (
+    <FreeLayoutEditorProvider {...editorProps}>
+      <EditorRenderer />
+    </FreeLayoutEditorProvider>
+  );
+};
+
+export default FlowGramApp;

+ 82 - 0
apps/docs/components/free-examples/step-6/initial-data.ts

@@ -0,0 +1,82 @@
+import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
+
+export const initialData: WorkflowJSON = {
+  nodes: [
+    {
+      id: '1',
+      type: 'start',
+      meta: {
+        position: { x: 0, y: 0 },
+      },
+      data: {
+        title: 'Start Node',
+      },
+    },
+    {
+      id: '2',
+      type: 'custom',
+      meta: {
+        position: { x: 400, y: -200 },
+      },
+      data: {
+        title: 'Custom Node A',
+      },
+    },
+    {
+      id: '3',
+      type: 'custom',
+      meta: {
+        position: { x: 400, y: 0 },
+      },
+      data: {
+        title: 'Custom Node B',
+      },
+    },
+    {
+      id: '4',
+      type: 'custom',
+      meta: {
+        position: { x: 400, y: 200 },
+      },
+      data: {
+        title: 'Custom Node C',
+      },
+    },
+    {
+      id: '5',
+      type: 'end',
+      meta: {
+        position: { x: 800, y: 0 },
+      },
+      data: {
+        title: 'End Node',
+      },
+    },
+  ],
+  edges: [
+    {
+      sourceNodeID: '1',
+      targetNodeID: '2',
+    },
+    {
+      sourceNodeID: '1',
+      targetNodeID: '3',
+    },
+    {
+      sourceNodeID: '1',
+      targetNodeID: '4',
+    },
+    {
+      sourceNodeID: '2',
+      targetNodeID: '5',
+    },
+    {
+      sourceNodeID: '3',
+      targetNodeID: '5',
+    },
+    {
+      sourceNodeID: '4',
+      targetNodeID: '5',
+    },
+  ],
+};

+ 30 - 0
apps/docs/components/free-examples/step-6/node-registries.tsx

@@ -0,0 +1,30 @@
+import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
+
+/**
+ * You can customize your own node registry
+ * 你可以自定义节点的注册器
+ */
+export const nodeRegistries: WorkflowNodeRegistry[] = [
+  {
+    type: 'start',
+    meta: {
+      isStart: true, // Mark as start
+      deleteDisable: true, // The start node cannot be deleted
+      copyDisable: true, // The start node cannot be copied
+      defaultPorts: [{ type: 'output' }], // Used to define the input and output ports, the start node only has the output port
+    },
+  },
+  {
+    type: 'end',
+    meta: {
+      deleteDisable: true,
+      copyDisable: true,
+      defaultPorts: [{ type: 'input' }],
+    },
+  },
+  {
+    type: 'custom',
+    meta: {},
+    defaultPorts: [{ type: 'output' }, { type: 'input' }], // A normal node has two ports
+  },
+];

+ 34 - 0
apps/docs/components/free-examples/step-6/node-render.tsx

@@ -0,0 +1,34 @@
+import '@flowgram.ai/free-layout-editor/index.css';
+
+import {
+  useNodeRender,
+  WorkflowNodeProps,
+  WorkflowNodeRenderer,
+} from '@flowgram.ai/free-layout-editor';
+
+export const NodeRender = (props: WorkflowNodeProps) => {
+  const { form, selected } = useNodeRender();
+  return (
+    <WorkflowNodeRenderer
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+      }}
+      node={props.node}
+    >
+      {form?.render()}
+    </WorkflowNodeRenderer>
+  );
+};

+ 58 - 0
apps/docs/components/free-examples/step-6/use-editor-props.tsx

@@ -0,0 +1,58 @@
+import { useMemo } from 'react';
+
+import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
+import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
+import { Field, FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
+
+import { NodeRender } from './node-render';
+import { nodeRegistries } from './node-registries';
+import { initialData } from './initial-data';
+
+export const useEditorProps = () =>
+  useMemo<FreeLayoutProps>(
+    () => ({
+      plugins: () => [createMinimapPlugin({}), createFreeSnapPlugin({})],
+      onAllLayersRendered: (ctx) => {
+        ctx.tools.fitView(false);
+      },
+      materials: {
+        renderDefaultNode: NodeRender,
+      },
+      nodeRegistries,
+      canDeleteNode: () => true,
+      canDeleteLine: () => true,
+      initialData,
+      /**
+       * Node engine enable, you can configure formMeta in the FlowNodeRegistry
+       */
+      nodeEngine: {
+        enable: true,
+      },
+      /**
+       * Redo/Undo enable
+       */
+      history: {
+        enable: true,
+        enableChangeNode: true, // Listen Node engine data change
+      },
+      getNodeDefaultRegistry(type) {
+        return {
+          type,
+          meta: {
+            defaultExpanded: true,
+          },
+          formMeta: {
+            /**
+             * Render form
+             */
+            render: () => (
+              <>
+                <Field<string> name="title">{({ field }) => <div>{field.value}</div>}</Field>
+              </>
+            ),
+          },
+        };
+      },
+    }),
+    []
+  );

+ 43 - 0
apps/docs/components/free-examples/step-7/add-node.tsx

@@ -0,0 +1,43 @@
+import {
+  useService,
+  WorkflowDocument,
+  WorkflowNodeEntity,
+  WorkflowSelectService,
+} from '@flowgram.ai/free-layout-editor';
+
+export const AddNode = () => {
+  const workflowDocument = useService(WorkflowDocument);
+  const selectService = useService(WorkflowSelectService);
+
+  return (
+    <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 8, display: 'flex', gap: 8 }}>
+      <button
+        style={{
+          border: '1px solid #e0e0e0',
+          borderRadius: '50%',
+          cursor: 'pointer',
+          padding: '4px',
+          color: '#ffffff',
+          background: '#7e72e8',
+          width: 70,
+          height: 70,
+          fontSize: 14,
+        }}
+        onClick={() => {
+          const node: WorkflowNodeEntity = workflowDocument.createWorkflowNodeByType(
+            'custom',
+            undefined, // position undefined means create node in center of canvas - position undefined 可以在画布中间创建节点
+            {
+              data: {
+                title: 'New Node',
+              },
+            }
+          );
+          selectService.selectNode(node);
+        }}
+      >
+        + Node
+      </button>
+    </div>
+  );
+};

+ 22 - 0
apps/docs/components/free-examples/step-7/app.tsx

@@ -0,0 +1,22 @@
+import '@flowgram.ai/free-layout-editor/index.css';
+
+import { FreeLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/free-layout-editor';
+
+import { useEditorProps } from './use-editor-props';
+import { Tools } from './tools';
+import { Minimap } from './minimap';
+import { AddNode } from './add-node';
+
+const FlowGramApp = () => {
+  const editorProps = useEditorProps();
+  return (
+    <FreeLayoutEditorProvider {...editorProps}>
+      <EditorRenderer />
+      <Tools />
+      <Minimap />
+      <AddNode />
+    </FreeLayoutEditorProvider>
+  );
+};
+
+export default FlowGramApp;

+ 82 - 0
apps/docs/components/free-examples/step-7/initial-data.ts

@@ -0,0 +1,82 @@
+import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
+
+export const initialData: WorkflowJSON = {
+  nodes: [
+    {
+      id: '1',
+      type: 'start',
+      meta: {
+        position: { x: 0, y: 0 },
+      },
+      data: {
+        title: 'Start Node',
+      },
+    },
+    {
+      id: '2',
+      type: 'custom',
+      meta: {
+        position: { x: 400, y: -200 },
+      },
+      data: {
+        title: 'Custom Node A',
+      },
+    },
+    {
+      id: '3',
+      type: 'custom',
+      meta: {
+        position: { x: 400, y: 0 },
+      },
+      data: {
+        title: 'Custom Node B',
+      },
+    },
+    {
+      id: '4',
+      type: 'custom',
+      meta: {
+        position: { x: 400, y: 200 },
+      },
+      data: {
+        title: 'Custom Node C',
+      },
+    },
+    {
+      id: '5',
+      type: 'end',
+      meta: {
+        position: { x: 800, y: 0 },
+      },
+      data: {
+        title: 'End Node',
+      },
+    },
+  ],
+  edges: [
+    {
+      sourceNodeID: '1',
+      targetNodeID: '2',
+    },
+    {
+      sourceNodeID: '1',
+      targetNodeID: '3',
+    },
+    {
+      sourceNodeID: '1',
+      targetNodeID: '4',
+    },
+    {
+      sourceNodeID: '2',
+      targetNodeID: '5',
+    },
+    {
+      sourceNodeID: '3',
+      targetNodeID: '5',
+    },
+    {
+      sourceNodeID: '4',
+      targetNodeID: '5',
+    },
+  ],
+};

+ 30 - 0
apps/docs/components/free-examples/step-7/minimap.tsx

@@ -0,0 +1,30 @@
+import { MinimapRender } from '@flowgram.ai/minimap-plugin';
+
+export const Minimap = () => (
+  <div
+    style={{
+      position: 'absolute',
+      right: 16,
+      bottom: 72,
+      zIndex: 100,
+      width: 118,
+    }}
+  >
+    <MinimapRender
+      containerStyles={{
+        pointerEvents: 'auto',
+        position: 'relative',
+        top: 'unset',
+        right: 'unset',
+        bottom: 'unset',
+        left: 'unset',
+      }}
+      inactiveStyle={{
+        opacity: 1,
+        scale: 1,
+        translateX: 0,
+        translateY: 0,
+      }}
+    />
+  </div>
+);

+ 30 - 0
apps/docs/components/free-examples/step-7/node-registries.tsx

@@ -0,0 +1,30 @@
+import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
+
+/**
+ * You can customize your own node registry
+ * 你可以自定义节点的注册器
+ */
+export const nodeRegistries: WorkflowNodeRegistry[] = [
+  {
+    type: 'start',
+    meta: {
+      isStart: true, // Mark as start
+      deleteDisable: true, // The start node cannot be deleted
+      copyDisable: true, // The start node cannot be copied
+      defaultPorts: [{ type: 'output' }], // Used to define the input and output ports, the start node only has the output port
+    },
+  },
+  {
+    type: 'end',
+    meta: {
+      deleteDisable: true,
+      copyDisable: true,
+      defaultPorts: [{ type: 'input' }],
+    },
+  },
+  {
+    type: 'custom',
+    meta: {},
+    defaultPorts: [{ type: 'output' }, { type: 'input' }], // A normal node has two ports
+  },
+];

+ 34 - 0
apps/docs/components/free-examples/step-7/node-render.tsx

@@ -0,0 +1,34 @@
+import '@flowgram.ai/free-layout-editor/index.css';
+
+import {
+  useNodeRender,
+  WorkflowNodeProps,
+  WorkflowNodeRenderer,
+} from '@flowgram.ai/free-layout-editor';
+
+export const NodeRender = (props: WorkflowNodeProps) => {
+  const { form, selected } = useNodeRender();
+  return (
+    <WorkflowNodeRenderer
+      style={{
+        width: 280,
+        minHeight: 88,
+        height: 'auto',
+        background: '#fff',
+        border: '1px solid rgba(6, 7, 9, 0.15)',
+        borderColor: selected ? '#4e40e5' : 'rgba(6, 7, 9, 0.15)',
+        borderRadius: 8,
+        boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        position: 'relative',
+        padding: 12,
+        cursor: 'move',
+      }}
+      node={props.node}
+    >
+      {form?.render()}
+    </WorkflowNodeRenderer>
+  );
+};

+ 90 - 0
apps/docs/components/free-examples/step-7/tools.tsx

@@ -0,0 +1,90 @@
+import { CSSProperties, useEffect, useState } from 'react';
+
+import { usePlaygroundTools, useClientContext, LineType } from '@flowgram.ai/free-layout-editor';
+
+export const Tools = () => {
+  const { history } = useClientContext();
+  const tools = usePlaygroundTools();
+  const [canUndo, setCanUndo] = useState(false);
+  const [canRedo, setCanRedo] = useState(false);
+
+  const buttonStyle: CSSProperties = {
+    border: '1px solid #e0e0e0',
+    borderRadius: '4px',
+    cursor: 'pointer',
+    padding: '4px',
+    color: '#141414',
+    background: '#e1e3e4',
+  };
+
+  useEffect(() => {
+    const disposable = history.undoRedoService.onChange(() => {
+      setCanUndo(history.canUndo());
+      setCanRedo(history.canRedo());
+    });
+    return () => disposable.dispose();
+  }, [history]);
+
+  return (
+    <div
+      style={{ position: 'absolute', zIndex: 10, bottom: 34, right: 16, display: 'flex', gap: 8 }}
+    >
+      <button style={buttonStyle} onClick={() => tools.zoomin()}>
+        ZoomIn
+      </button>
+      <button style={buttonStyle} onClick={() => tools.zoomout()}>
+        ZoomOut
+      </button>
+      <span
+        style={{
+          ...buttonStyle,
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          cursor: 'default',
+          width: 40,
+        }}
+      >
+        {Math.floor(tools.zoom * 100)}%
+      </span>
+      <button style={buttonStyle} onClick={() => tools.fitView()}>
+        FitView
+      </button>
+      <button style={buttonStyle} onClick={() => tools.autoLayout()}>
+        AutoLayout
+      </button>
+      <button
+        style={buttonStyle}
+        onClick={() =>
+          tools.switchLineType(
+            tools.lineType === LineType.BEZIER ? LineType.LINE_CHART : LineType.BEZIER
+          )
+        }
+      >
+        {tools.lineType === LineType.BEZIER ? 'Bezier' : 'Fold'}
+      </button>
+      <button
+        style={{
+          ...buttonStyle,
+          cursor: canUndo ? 'pointer' : 'not-allowed',
+          color: canUndo ? '#141414' : '#b1b1b1',
+        }}
+        onClick={() => history.undo()}
+        disabled={!canUndo}
+      >
+        Undo
+      </button>
+      <button
+        style={{
+          ...buttonStyle,
+          cursor: canRedo ? 'pointer' : 'not-allowed',
+          color: canRedo ? '#141414' : '#b1b1b1',
+        }}
+        onClick={() => history.redo()}
+        disabled={!canRedo}
+      >
+        Redo
+      </button>
+    </div>
+  );
+};

+ 68 - 0
apps/docs/components/free-examples/step-7/use-editor-props.tsx

@@ -0,0 +1,68 @@
+import { useMemo } from 'react';
+
+import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
+import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
+import { Field, FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
+
+import { NodeRender } from './node-render';
+import { nodeRegistries } from './node-registries';
+import { initialData } from './initial-data';
+
+export const useEditorProps = () =>
+  useMemo<FreeLayoutProps>(
+    () => ({
+      plugins: () => [
+        createMinimapPlugin({
+          disableLayer: true,
+          canvasStyle: {
+            canvasWidth: 100,
+            canvasHeight: 50,
+            canvasPadding: 50,
+          },
+        }),
+        createFreeSnapPlugin({}),
+      ],
+      onAllLayersRendered: (ctx) => {
+        ctx.tools.fitView(false);
+      },
+      materials: {
+        renderDefaultNode: NodeRender,
+      },
+      nodeRegistries,
+      canDeleteNode: () => true,
+      canDeleteLine: () => true,
+      initialData,
+      /**
+       * Node engine enable, you can configure formMeta in the FlowNodeRegistry
+       */
+      nodeEngine: {
+        enable: true,
+      },
+      /**
+       * Redo/Undo enable
+       */
+      history: {
+        enable: true,
+        enableChangeNode: true, // Listen Node engine data change
+      },
+      getNodeDefaultRegistry(type) {
+        return {
+          type,
+          meta: {
+            defaultExpanded: true,
+          },
+          formMeta: {
+            /**
+             * Render form
+             */
+            render: () => (
+              <>
+                <Field<string> name="title">{({ field }) => <div>{field.value}</div>}</Field>
+              </>
+            ),
+          },
+        };
+      },
+    }),
+    []
+  );

+ 11 - 7
apps/docs/global.less

@@ -7,15 +7,12 @@
   width: auto;
   max-width: 1400px;
 }
+
 .rspress-doc-container:not(:has(.aside-container_edeb4)) .rspress-doc-footer {
   width: auto;
   max-width: 1400px;
 }
 
-.sp-preview {
-  height: 100% !important;
-}
-
 //.medium-zoom-image {
 //  width: 100%;
 //}
@@ -27,6 +24,7 @@
 .rs-table {
   width: 100%;
 }
+
 .rs-table td {
   padding: 8px;
   border: 1px solid var(--rp-c-divider-light);
@@ -36,6 +34,7 @@
   max-height: 500px;
   max-width: 500px;
 }
+
 .rs-table td:first-child {
   width: 20%;
 }
@@ -74,15 +73,19 @@
 
 .light-mode {
   color-scheme: light;
-  --background-color: #ffffff !important; /* 强制使用浅色背景 */
-  --text-color: #000000 !important; /* 强制使用深色文本 */
+  --background-color: #ffffff !important;
+  /* 强制使用浅色背景 */
+  --text-color: #000000 !important;
+  /* 强制使用深色文本 */
   --rp-c-bg: #fff !important;
-  --rp-c-text-1: #000000 !important; /* 强制使用深色文本 */
+  --rp-c-text-1: #000000 !important;
+  /* 强制使用深色文本 */
 
   button {
     background: transparent;
   }
 }
+
 .preview-ediitor {
   button {
     color: #000000e6;
@@ -100,6 +103,7 @@
     transition: background .3s, border .3s, opacity .3s;
     display: inline-flex
   }
+
   input {
     border: 1px solid #e5e6eb;
     background-color: var(--rp-c-bg);

+ 8 - 7
apps/docs/rspress.config.ts

@@ -58,11 +58,12 @@ export default defineConfig({
       /\/auto-docs\//,
       // these pages do not support SSR
       // document is not defined
+      '/index',
       '/en/examples/node-form/basic',
       '/en/examples/node-form/array',
       '/en/examples/node-form/dynamic',
-      '/en/guide/getting-started/create-fixed-layout-simple',
-      '/en/guide/getting-started/create-free-layout-simple',
+      '/en/guide/getting-started/free-layout',
+      '/en/guide/getting-started/fixed-layout',
       '/en/examples/node-form/effect',
       '/en/guide/fixed-layout/composite-nodes',
       '/en/examples/playground',
@@ -76,8 +77,8 @@ export default defineConfig({
       '/examples/node-form/basic',
       '/examples/node-form/array',
       '/examples/node-form/dynamic',
-      '/guide/getting-started/create-fixed-layout-simple',
-      '/guide/getting-started/create-free-layout-simple',
+      '/guide/getting-started/free-layout',
+      '/guide/getting-started/fixed-layout',
       '/examples/node-form/effect',
       '/guide/fixed-layout/composite-nodes',
       '/examples/playground',
@@ -104,10 +105,10 @@ export default defineConfig({
       description: '静态网站生成器',
     },
   ],
-  icon: '/logo.png',
+  icon: '/flowgram-logo.svg',
   logo: {
-    light: '/logo.png',
-    dark: '/logo.png',
+    light: '/flowgram-logo.svg',
+    dark: '/flowgram-logo.svg',
   },
   lang: 'zh',
   logoText: 'FlowGram.AI',

+ 1 - 1
apps/docs/src/en/_nav.json

@@ -1,7 +1,7 @@
 [
   {
     "text": "Guide",
-    "link": "/guide/introduction",
+    "link": "/guide/getting-started/introduction",
     "activeMatch": "/guide/"
   },
   {

+ 4 - 4
apps/docs/src/en/guide/_meta.json

@@ -1,6 +1,4 @@
 [
-  "introduction",
-  "contact-us",
   {
     "type": "dir",
     "name": "getting-started",
@@ -44,6 +42,8 @@
   {
     "type": "dir",
     "name": "runtime",
-    "label": "Runtime (WIP)"
-  }
+    "label": "Runtime"
+  },
+  "contributing",
+  "contact-us"
 ]

+ 0 - 3
apps/docs/src/en/guide/contact-us.mdx

@@ -2,6 +2,3 @@
 
 - Issues: [Issues](https://github.com/bytedance/flowgram.ai/issues)
 - Discord: https://discord.gg/SwDWdrgA9f
-- Lark: Scan the QR code below with [Register Feishu](https://www.feishu.cn/en/) to join our FlowGram user group.
-
-<img src="/lark-group.png" width="200"/>

+ 112 - 0
apps/docs/src/en/guide/contributing.mdx

@@ -0,0 +1,112 @@
+# Contribution Guide
+
+This document helps you quickly develop, test, and submit PRs in this repository. The repository uses a Monorepo managed by Rush + pnpm, and the documentation site is built with Rspress.
+
+## Setting Up the Development Environment
+
+1.  **Install Node.js 18+** (LTS/Hydrogen recommended)
+
+    ```bash
+    nvm install lts/hydrogen
+    nvm alias default lts/hydrogen # Set as the default Node version
+    nvm use lts/hydrogen
+    ```
+
+2.  **Clone the repository locally**
+
+    ```bash
+    git clone git@github.com:bytedance/flowgram.ai.git
+    ```
+
+3.  **Install global dependencies**
+
+    ```bash
+    npm i -g pnpm@10.6.5 @microsoft/rush@5.150.0
+    ```
+
+4.  **Install project dependencies**
+
+    ```bash
+    rush install
+    ```
+
+5.  **Build the project**
+
+    ```bash
+    rush build
+    ```
+
+6.  **Run the documentation or examples**
+
+    ```bash
+    rush dev:docs                  # Start the documentation site in apps/docs (with incremental build)
+    rush dev:demo-fixed-layout     # Run the fixed layout example
+    rush dev:demo-free-layout      # Run the free layout example
+    ```
+
+## Common Commands (Rush Custom)
+
+```bash
+rush build            # Build all packages
+rush build:watch      # Incrementally build and watch
+rush lint             # Run ESLint checks
+rush lint:fix         # Automatically fix ESLint issues
+rush ts-check         # TypeScript type checking
+rush test             # Run test scripts for each package (aggregated by package)
+rush e2e:test         # Run all e2e tests
+rush e2e:update-screenshot # Update e2e snapshots
+rush dep-check        # Automatically check dependency health
+```
+
+## Branch and Commit Conventions
+
+-   Branch naming:
+    -   `feat/description` (new feature)
+    -   `fix/description` (bug fix)
+    -   `docs/description` (documentation changes)
+    -   `chore/description` (maintenance/miscellaneous)
+-   Commit messages (Conventional Commits):
+    -   Format: `type(scope): subject`, for example:
+
+        ```text
+        feat(editor): Support batch alignment of nodes
+        ```
+
+    -   Common types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
+    -   The repository has commitlint validation enabled (commit-msg hook), so commit messages will be automatically checked; pre-commit will also run lint-staged (automatically update license headers, fix eslint issues) and `rush check`.
+
+## Development and Quality Assurance
+
+-   Local development recommendations:
+    -   First, run `rush build:watch`, then run the development command in the corresponding demo or docs directory (e.g., `rush dev:docs`).
+    -   After modifying the code, ensure it passes: `rush lint`, `rush ts-check`, `rush build`, `rush test`.
+-   Testing instructions:
+    -   e2e test cases are located in the `e2e/` directory and can be run with `rush e2e:test`, or you can update snapshots with `rush e2e:update-screenshot`.
+
+## Pull Request Process
+
+1.  Create your working branch from `main` (following the branch naming conventions).
+2.  Code and add tests/documentation.
+3.  Pass local quality checks (lint, ts-check, build, test).
+4.  Submit a PR: fill in the description, link the Issue, and use the template.
+5.  Review and CI: Maintainers will review the code, and it can be merged after the CI passes.
+
+## Documentation Contribution
+
+-   Documentation location: `apps/docs/src/zh/**` (Chinese) and `apps/docs/src/en/**` (English).
+-   Local preview: Run `rush dev:docs` to start the Rspress documentation site.
+-   To automatically generate API documentation, you can run `rushx docs` in the `apps/docs` directory (which calls a script to generate it).
+
+## Common Issues
+
+-   pnpm-lock merge conflicts: The repository has a merge strategy configured in the `post-checkout` hook, which usually avoids lock file conflicts.
+-   Node version: Please ensure you are using Node 18+, otherwise you may encounter dependency or build failures.
+
+## Reporting Issues
+
+-   Please submit an Issue on GitHub: https://github.com/bytedance/flowgram.ai/issues/new/choose
+    -   Describe the problem, reproduction steps, expected and actual behavior, and provide a code example if necessary.
+
+## License
+
+-   This project is licensed under the MIT License. By submitting code, you agree to the relevant terms.

+ 65 - 2
apps/docs/src/en/guide/free-layout/node.mdx

@@ -1,4 +1,4 @@
-# Node
+# Nodes
 
 Nodes are defined through [FlowNodeEntity](/api/core/flow-node-entity.html)
 
@@ -52,7 +52,70 @@ const nodeData: FlowNodeJSON = {
 
 ## Node Definition
 
-In free layout scenarios, node definition is used to declare node's initial position/size, ports, form rendering, etc. For details, see [Declare Node](/guide/getting-started/create-free-layout-simple.html#4-declare-nodes)
+In free layout scenarios, node definition is used to declare node's initial position/size, ports, form rendering, etc.
+
+```tsx pure title="node-registries.tsx"
+import { WorkflowNodeRegistry, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
+
+/**
+ * You can customize your own node registry
+ */
+export const nodeRegistries: WorkflowNodeRegistry[] = [
+  {
+    type: 'start',
+    meta: {
+      isStart: true, // Mark as start node
+      deleteDisable: true, // Start node cannot be deleted
+      copyDisable: true, // Start node cannot be copied
+      defaultPorts: [{ type: 'output' }], // Define node input and output ports, start node only has output port
+      // useDynamicPort: true, // For dynamic ports, will look for DOM with data-port-id and data-port-type attributes as ports
+    },
+    /**
+     * Configure node form validation and rendering
+     * Note: validate uses data and rendering separation to ensure node validation even without rendering
+     */
+    formMeta: {
+      validateTrigger: ValidateTrigger.onChange,
+      validate: {
+        title: ({ value }) => (value ? undefined : 'Title is required'),
+      },
+      /**
+       * Render form
+       */
+      render: () => (
+       <>
+          <Field name="title">
+            {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
+          </Field>
+          <Field name="content">
+            {({ field }) => <input onChange={field.onChange} value={field.value}/>}
+          </Field>
+        </>
+      )
+    },
+  },
+  {
+    type: 'end',
+    meta: {
+      deleteDisable: true,
+      copyDisable: true,
+      defaultPorts: [{ type: 'input' }],
+    },
+    formMeta: {
+      // ...
+    }
+  },
+  {
+    type: 'custom',
+    meta: {
+    },
+    formMeta: {
+      // ...
+    },
+    defaultPorts: [{ type: 'output' }, { type: 'input' }], // Normal nodes have two ports
+  },
+];
+```
 
 ## Get Current Rendering Node
 

+ 4 - 3
apps/docs/src/en/guide/getting-started/_meta.json

@@ -1,5 +1,6 @@
 [
-  "install",
-  "create-fixed-layout-simple",
-  "create-free-layout-simple"
+  "introduction",
+  "quick-start",
+  "free-layout",
+  "fixed-layout"
 ]

+ 0 - 365
apps/docs/src/en/guide/getting-started/create-fixed-layout-simple.mdx

@@ -1,365 +0,0 @@
-# Creating a Fixed Layout Canvas
-
-This example can be installed using `npx @flowgram.ai/create-app@latest fixed-layout-simple`. For complete code and demo, see:
-
-<div className="rs-tip">
-  <a className="rs-link" href="/en/examples/fixed-layout/fixed-layout-simple.html">
-    Fixed Layout Basic Usage
-  </a>
-</div>
-
-File structure:
-
-```
-- hooks
-  - use-editor-props.ts # Canvas configuration
-- components
-  - base-node.tsx # Node rendering
-  - tools.tsx # Canvas toolbar
-- initial-data.ts # Initialization data
-- node-registries.ts # Node configuration
-- app.tsx # Canvas entry
-```
-
-### 1. Canvas Entry
-
-- `FixedLayoutEditorProvider`: Canvas configurator that generates react-context internally for child components to consume
-- `EditorRenderer`: The final rendered canvas that can be wrapped under other components for customizing canvas position
-
-```tsx pure title="app.tsx"
-import {
-  FixedLayoutEditorProvider,
-  EditorRenderer,
-} from '@flowgram.ai/fixed-layout-editor';
-
-import '@flowgram.ai/fixed-layout-editor/index.css'; // Load styles
-
-import { useEditorProps } from './hooks/use-editor-props' // Detailed canvas props configuration
-import { Tools } from './components/tools' // Canvas tools
-
-function App() {
-  const editorProps = useEditorProps()
-  return (
-    <FixedLayoutEditorProvider {...editorProps}>
-      <EditorRenderer className="demo-editor" />
-      <Tools />
-    </FixedLayoutEditorProvider>
-  );
-}
-```
-
-### 2. Configure Canvas
-
-Canvas configuration is declarative, providing configurations for data, rendering, events, and plugins.
-
-```tsx pure title="hooks/use-editor-props.tsx"
-import { useMemo } from 'react';
-import { type FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor';
-import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
-import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
-
-import { initialData } from './initial-data' // Initialization data
-import { nodeRegistries } from './node-registries' // Node declaration configuration
-import { BaseNode } from './base-node' // Node rendering
-
-export function useEditorProps(
-): FixedLayoutProps {
-  return useMemo<FixedLayoutProps>(
-    () => ({
-      /**
-       * Initialization data
-       */
-      initialData,
-      /**
-       * Canvas node definitions
-       */
-      nodeRegistries,
-      /**
-       * UI components can be customized through keys. Here, a set of semi components is provided for quick validation.
-       * For deep customization, refer to:
-       * https://github.com/bytedance/flowgram.ai/blob/main/packages/materials/fixed-semi-materials/src/components/index.tsx
-       */
-      materials: {
-        components: {
-          ...defaultFixedSemiMaterials,
-          // [FlowRendererKey.ADDER]: NodeAdder,
-          // [FlowRendererKey.BRANCH_ADDER]: BranchAdder,
-        },
-        renderDefaultNode: BaseNode, // Node rendering component
-      },
-      /**
-       * Node engine for rendering node forms
-       */
-      nodeEngine: {
-        enable: true,
-      },
-      /**
-       * Canvas history record for controlling redo/undo
-       */
-      history: {
-        enable: true,
-        enableChangeNode: true, // Monitor node form data changes
-      },
-      /**
-       * Canvas initialization callback
-       */
-      onInit: ctx => {
-        // For dynamic data loading, use the following method asynchronously
-        // ctx.docuemnt.fromJSON(initialData)
-      },
-      /**
-       * Callback when canvas first render completes
-       */
-      onAllLayersRendered: (ctx) => {},
-      /**
-       * Canvas disposal callback
-       */
-      onDispose: () => { },
-      plugins: () => [
-        /**
-         * Minimap plugin
-         */
-        createMinimapPlugin({}),
-      ],
-    }),
-    [],
-  );
-}
-```
-
-### 3. Configure Data
-
-Canvas document data uses a tree structure that supports nesting.
-
-:::note Document Data Basic Structure:
-
-- nodes `array` Node list, supports nesting
-
-:::
-
-:::note Node Data Basic Structure:
-
-- id: `string` Unique node identifier, must be unique
-- meta: `object` Node UI configuration information, such as position information for free layout
-- type: `string | number` Node type, corresponds to `type` in `nodeRegistries`
-- data: `object` Node form data
-- blocks: `array` Node branches, using `block` is closer to `Gramming`
-
-:::
-
-```tsx pure title="initial-data.tsx"
-import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
-
-/**
- * Configure flow data, data is in blocks nested format
- */
-export const initialData: FlowDocumentJSON = {
-  nodes: [
-    // Start node
-    {
-      id: 'start_0',
-      type: 'start',
-      data: {
-        title: 'Start',
-        content: 'start content'
-      },
-      blocks: [],
-    },
-    // Condition node
-    {
-      id: 'condition_0',
-      type: 'condition',
-      data: {
-        title: 'Condition'
-      },
-      blocks: [
-        {
-          id: 'branch_0',
-          type: 'block',
-          data: {
-            title: 'Branch 0',
-            content: 'branch 1 content'
-          },
-          blocks: [
-            {
-              id: 'custom_0',
-              type: 'custom',
-              data: {
-                title: 'Custom',
-                content: 'custom content'
-              },
-            },
-          ],
-        },
-        {
-          id: 'branch_1',
-          type: 'block',
-          data: {
-            title: 'Branch 1',
-            content: 'branch 1 content'
-          },
-          blocks: [],
-        },
-      ],
-    },
-    // End node
-    {
-      id: 'end_0',
-      type: 'end',
-      data: {
-        title: 'End',
-        content: 'end content'
-      },
-    },
-  ],
-};
-```
-
-### 4. Declare Nodes
-
-Node declaration can be used to determine node types and rendering methods.
-
-```tsx pure title="node-registries.tsx"
-import { FlowNodeRegistry, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
-
-/**
- * Custom node registration
- */
-export const nodeRegistries: FlowNodeRegistry[] = [
-  {
-    /**
-     * Custom node type
-     */
-    type: 'condition',
-    /**
-     * Custom node extensions:
-     *  - loop: Extend as loop node
-     *  - start: Extend as start node
-     *  - dynamicSplit: Extend as branch node
-     *  - end: Extend as end node
-     *  - tryCatch: Extend as tryCatch node
-     *  - default: Extend as normal node (default)
-     */
-    extend: 'dynamicSplit',
-    /**
-     * Node configuration information
-     */
-    meta: {
-      // isStart: false, // Whether it's a start node
-      // isNodeEnd: false, // Whether it's an end node, no nodes can be added after end node
-      // draggable: false, // Whether draggable, start and end nodes cannot be dragged
-      // selectable: false, // Triggers and start nodes cannot be box-selected
-      // deleteDisable: true, // Disable deletion
-      // copyDisable: true, // Disable copying
-      // addDisable: true, // Disable adding
-    },
-    /**
-     * Configure node form validation and rendering
-     * Note: validate uses data and rendering separation to ensure node validation even without rendering
-     */
-    formMeta: {
-      validateTrigger: ValidateTrigger.onChange,
-      validate: {
-        title: ({ value }) => (value ? undefined : 'Title is required'),
-      },
-      /**
-       * Render form
-       */
-      render: () => (
-       <>
-          <Field name="title">
-            {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
-          </Field>
-          <Field name="content">
-            {({ field }) => <input onChange={field.onChange} value={field.value}/>}
-          </Field>
-        </>
-      )
-    },
-  },
-];
-```
-
-### 5. Render Nodes
-
-Node rendering is used to add styles, events, and form rendering positions.
-
-```tsx pure title="components/base-node.tsx"
-import { useNodeRender } from '@flowgram.ai/fixed-layout-editor';
-
-export const BaseNode = () => {
-  /**
-   * Provides node rendering related methods
-   */
-  const nodeRender = useNodeRender();
-  /**
-   * Form can only be used when node engine is enabled
-   */
-  const form = nodeRender.form;
-
-  return (
-    <div
-      className="demo-fixed-node"
-      onMouseEnter={nodeRender.onMouseEnter}
-      onMouseLeave={nodeRender.onMouseLeave}
-      onMouseDown={e => {
-        // Trigger drag
-        nodeRender.startDrag(e);
-        e.stopPropagation();
-      }}
-      style={{
-        // BlockOrderIcon represents the first node of a branch, BlockIcon represents the header node of the entire condition
-        ...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
-        outline: form?.state.invalid ? '1px solid red' : 'none', // Red border for form validation errors
-      }}
-    >
-      {
-        // Form rendering generated through formMeta
-        form?.render()
-      }
-    </div>
-  );
-};
-```
-
-### 6. Add Tools
-
-Tools are mainly used to control canvas zooming and other operations. Tools are collected in `usePlaygroundTools`, while `useClientContext` is used to get canvas context, which contains core modules like `history`.
-
-```tsx pure title="components/tools.tsx"
-import { useEffect, useState } from 'react'
-import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
-
-export function Tools() {
-  const { history } = useClientContext();
-  const tools = usePlaygroundTools();
-  const [canUndo, setCanUndo] = useState(false);
-  const [canRedo, setCanRedo] = useState(false);
-
-  useEffect(() => {
-    const disposable = history.undoRedoService.onChange(() => {
-      setCanUndo(history.canUndo());
-      setCanRedo(history.canRedo());
-    });
-    return () => disposable.dispose();
-  }, [history]);
-
-  return <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 16, display: 'flex', gap: 8 }}>
-    <button onClick={() => tools.zoomin()}>ZoomIn</button>
-    <button onClick={() => tools.zoomout()}>ZoomOut</button>
-    <button onClick={() => tools.fitView()}>Fitview</button>
-    <button onClick={() => tools.changeLayout()}>ChangeLayout</button>
-    <button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
-    <button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
-    <span>{Math.floor(tools.zoom * 100)}%</span>
-  </div>
-}
-```
-
-### 7. Result
-
-import { FixedLayoutSimple } from '../../../../components';
-
-<div style={{ position: 'relative', width: '100%', height: '600px'}}>
-  <FixedLayoutSimple />
-</div>

+ 0 - 339
apps/docs/src/en/guide/getting-started/create-free-layout-simple.mdx

@@ -1,339 +0,0 @@
-# Creating a Free Layout Canvas
-
-This example can be installed via `npx @flowgram.ai/create-app@latest free-layout-simple`. For complete code and demo, see:
-
-<div className="rs-tip">
-  <a className="rs-link" href="/en/examples/free-layout/free-layout-simple.html">
-    Free Layout Basic Usage
-  </a>
-</div>
-
-File structure:
-
-```
-- hooks
-  - use-editor-props.ts # Canvas configuration
-- components
-  - base-node.tsx # Node rendering
-  - tools.tsx # Canvas toolbar
-- initial-data.ts # Initialization data
-- node-registries.ts # Node configuration
-- app.tsx # Canvas entry
-```
-
-### 1. Canvas Entry
-
-- `FreeLayoutEditorProvider`: Canvas configurator that generates react-context internally for child components to consume
-- `EditorRenderer`: The final rendered canvas that can be wrapped under other components for customizing canvas position
-
-```tsx pure title="app.tsx"
-import {
-  FreeLayoutEditorProvider,
-  EditorRenderer,
-} from '@flowgram.ai/free-layout-editor';
-
-import '@flowgram.ai/free-layout-editor/index.css'; // Load styles
-
-import { useEditorProps } from './hooks/use-editor-props' // Detailed canvas props configuration
-import { Tools } from './components/tools' // Canvas tools
-
-function App() {
-  const editorProps = useEditorProps()
-  return (
-    <FreeLayoutEditorProvider {...editorProps}>
-      <EditorRenderer className="demo-editor" />
-      <Tools />
-    </FreeLayoutEditorProvider>
-  );
-}
-```
-
-### 2. Configure Canvas
-
-Canvas configuration is declarative, providing data, rendering, events, and plugin-related configurations
-
-```tsx pure title="hooks/use-editor-props.tsx"
-import { useMemo } from 'react';
-import { type FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
-import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
-
-import { initialData } from './initial-data' // Initialization data
-import { nodeRegistries } from './node-registries' // Node declaration configuration
-import { BaseNode } from './components/base-node' // Node rendering
-
-export function useEditorProps(
-): FreeLayoutProps {
-  return useMemo<FreeLayoutProps>(
-    () => ({
-      /**
-       * Initialization data
-       */
-      initialData,
-      /**
-       * Canvas node definitions
-       */
-      nodeRegistries,
-      /**
-       * Materials
-       */
-      materials: {
-        renderDefaultNode: BaseNode, // Node rendering component
-      },
-      /**
-       * Node engine for rendering node forms
-       */
-      nodeEngine: {
-        enable: true,
-      },
-      /**
-       * Canvas history record for controlling redo/undo
-       */
-      history: {
-        enable: true,
-        enableChangeNode: true, // For monitoring node form data changes
-      },
-      /**
-       * Canvas initialization callback
-       */
-      onInit: ctx => {
-        // For dynamic data loading, you can use the following method asynchronously
-        // ctx.docuemnt.fromJSON(initialData)
-      },
-      /**
-       * Callback when canvas first renders completely
-       */
-      onAllLayersRendered: (ctx) => {},
-      /**
-       * Canvas destruction callback
-       */
-      onDispose: () => { },
-      plugins: () => [
-        /**
-         * Minimap plugin
-         */
-        createMinimapPlugin({}),
-      ],
-    }),
-    [],
-  );
-}
-```
-
-### 3. Configure Data
-
-Canvas document data uses a tree structure that supports nesting
-
-:::note Document Data Basic Structure:
-
-- nodes `array` Node list, supports nesting
-- edges `array` Edge list
-
-:::
-
-:::note Node Data Basic Structure:
-
-- id: `string` Unique node identifier, must be unique
-- meta: `object` Node UI configuration information, such as free layout `position` information
-- type: `string | number` Node type, corresponds to `type` in `nodeRegistries`
-- data: `object` Node form data, customizable by business
-- blocks: `array` Node branches, using `block` is closer to `Gramming`
-- edges: `array` Edges for the sub-canvas
-
-:::
-
-:::note Edge Data Basic Structure:
-
-- sourceNodeID: `string` Start node id
-- targetNodeID: `string` Target node id
-- sourcePortID?: `string | number` Start port id, defaults to start node's default port if omitted
-- targetPortID?: `string | number` Target port id, defaults to target node's default port if omitted
-
-:::
-
-```tsx pure title="initial-data.ts"
-import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
-
-export const initialData: WorkflowJSON = {
-  nodes: [
-    {
-      id: 'start_0',
-      type: 'start',
-      meta: {
-        position: { x: 0, y: 0 },
-      },
-      data: {
-        title: 'Start',
-        content: 'Start content'
-      },
-    },
-    {
-      id: 'node_0',
-      type: 'custom',
-      meta: {
-        position: { x: 400, y: 0 },
-      },
-      data: {
-        title: 'Custom',
-        content: 'Custom node content'
-      },
-    },
-    {
-      id: 'end_0',
-      type: 'end',
-      meta: {
-        position: { x: 800, y: 0 },
-      },
-      data: {
-        title: 'End',
-        content: 'End content'
-      },
-    },
-  ],
-  edges: [
-    {
-      sourceNodeID: 'start_0',
-      targetNodeID: 'node_0',
-    },
-    {
-      sourceNodeID: 'node_0',
-      targetNodeID: 'end_0',
-    },
-  ],
-};
-```
-
-### 4. Declare Nodes
-
-Node declarations can be used to determine node types and rendering methods
-
-```tsx pure title="node-registries.tsx"
-import { WorkflowNodeRegistry, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
-
-/**
- * You can customize your own node registry
- */
-export const nodeRegistries: WorkflowNodeRegistry[] = [
-  {
-    type: 'start',
-    meta: {
-      isStart: true, // Mark as start node
-      deleteDisable: true, // Start node cannot be deleted
-      copyDisable: true, // Start node cannot be copied
-      defaultPorts: [{ type: 'output' }], // Define node input and output ports, start node only has output port
-      // useDynamicPort: true, // For dynamic ports, will look for DOM with data-port-id and data-port-type attributes as ports
-    },
-    /**
-     * Configure node form validation and rendering
-     * Note: validate uses data and rendering separation to ensure node validation even without rendering
-     */
-    formMeta: {
-      validateTrigger: ValidateTrigger.onChange,
-      validate: {
-        title: ({ value }) => (value ? undefined : 'Title is required'),
-      },
-      /**
-       * Render form
-       */
-      render: () => (
-       <>
-          <Field name="title">
-            {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
-          </Field>
-          <Field name="content">
-            {({ field }) => <input onChange={field.onChange} value={field.value}/>}
-          </Field>
-        </>
-      )
-    },
-  },
-  {
-    type: 'end',
-    meta: {
-      deleteDisable: true,
-      copyDisable: true,
-      defaultPorts: [{ type: 'input' }],
-    },
-    formMeta: {
-      // ...
-    }
-  },
-  {
-    type: 'custom',
-    meta: {
-    },
-    formMeta: {
-      // ...
-    },
-    defaultPorts: [{ type: 'output' }, { type: 'input' }], // Normal nodes have two ports
-  },
-];
-```
-
-### 5. Render Nodes
-
-Node rendering is used to add styles, events, and form rendering positions
-
-```tsx pure title="components/base-node.tsx"
-import { useNodeRender, WorkflowNodeRenderer } from '@flowgram.ai/free-layout-editor';
-
-export const BaseNode = () => {
-  /**
-   * Provides node rendering related methods
-   */
-  const { form } = useNodeRender()
-  /**
-   * WorkflowNodeRenderer adds node drag events and port rendering, for deep customization, see the component source code:
-   * https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/components/workflow-node-renderer.tsx
-   */
-  return (
-    <WorkflowNodeRenderer className="demo-free-node" node={props.node}>
-      {
-        // Form rendering generated through formMeta
-        form?.render()
-      }
-    </WorkflowNodeRenderer>
-  )
-};
-```
-
-### 6. Add Tools
-
-Tools are mainly used to control canvas zoom and other operations. Tools are collected in `usePlaygroundTools`, while `useClientContext` is used to get canvas context, which contains core canvas modules like `history`
-
-```tsx pure title="components/tools.tsx"
-import { useEffect, useState } from 'react'
-import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
-
-export function Tools() {
-  const { history } = useClientContext();
-  const tools = usePlaygroundTools();
-  const [canUndo, setCanUndo] = useState(false);
-  const [canRedo, setCanRedo] = useState(false);
-
-  useEffect(() => {
-    const disposable = history.undoRedoService.onChange(() => {
-      setCanUndo(history.canUndo());
-      setCanRedo(history.canRedo());
-    });
-    return () => disposable.dispose();
-  }, [history]);
-
-  return <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}>
-    <button onClick={() => tools.zoomin()}>ZoomIn</button>
-    <button onClick={() => tools.zoomout()}>ZoomOut</button>
-    <button onClick={() => tools.fitView()}>Fitview</button>
-    <button onClick={() => tools.autoLayout()}>AutoLayout</button>
-    <button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
-    <button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
-    <span>{Math.floor(tools.zoom * 100)}%</span>
-  </div>
-}
-```
-
-### 7. Result
-
-import { FreeLayoutSimple } from '../../../../components';
-
-<div style={{ position: 'relative', width: '100%', height: '600px'}}>
-  <FreeLayoutSimple />
-</div>

+ 370 - 0
apps/docs/src/en/guide/getting-started/fixed-layout.mdx

@@ -0,0 +1,370 @@
+# Fixed Layout
+
+import {
+  PackageManagerTabs
+  // @ts-ignore
+} from '@theme';
+import { FixedLayoutCodePreview } from '@components/code-preview';
+import step1 from '@components/fixed-examples/step-1.tsx?raw';
+import step2 from '@components/fixed-examples/step-2.tsx?raw';
+import step3 from '@components/fixed-examples/step-3.tsx?raw';
+import step4 from '@components/fixed-examples/step-4.tsx?raw';
+import step5App from '@components/fixed-examples/step-5/app.tsx?raw';
+import step5UseEditorProps from '@components/fixed-examples/step-5/use-editor-props.tsx?raw';
+import step5InitialData from '@components/fixed-examples/step-5/initial-data.ts?raw';
+import step5NodeRegistries from '@components/fixed-examples/step-5/node-registries.tsx?raw';
+import step5NodeRender from '@components/fixed-examples/step-5/node-render.tsx?raw';
+import step5Adder from '@components/fixed-examples/step-5/adder.tsx?raw';
+import step6App from '@components/fixed-examples/step-6/app.tsx?raw';
+import step6UseEditorProps from '@components/fixed-examples/step-6/use-editor-props.tsx?raw';
+import step6InitialData from '@components/fixed-examples/step-6/initial-data.ts?raw';
+import step6NodeRegistries from '@components/fixed-examples/step-6/node-registries.tsx?raw';
+import step6NodeRender from '@components/fixed-examples/step-6/node-render.tsx?raw';
+import step6Adder from '@components/fixed-examples/step-6/adder.tsx?raw';
+import step7App from '@components/fixed-examples/step-7/app.tsx?raw';
+import step7UseEditorProps from '@components/fixed-examples/step-7/use-editor-props.tsx?raw';
+import step7InitialData from '@components/fixed-examples/step-7/initial-data.ts?raw';
+import step7NodeRegistries from '@components/fixed-examples/step-7/node-registries.tsx?raw';
+import step7NodeRender from '@components/fixed-examples/step-7/node-render.tsx?raw';
+import step7Adder from '@components/fixed-examples/step-7/adder.tsx?raw';
+import step7Tools from '@components/fixed-examples/step-7/tools.tsx?raw';
+import step7Minimap from '@components/fixed-examples/step-7/minimap.tsx?raw';
+
+
+
+## Step 0: Install Dependencies
+
+1.  Install the editor packages:
+
+<PackageManagerTabs command={{
+  "npm": "npm install @flowgram.ai/fixed-layout-editor @flowgram.ai/fixed-semi-materials",
+  "pnpm": "pnpm add @flowgram.ai/fixed-layout-editor @flowgram.ai/fixed-semi-materials",
+  "yarn": "yarn add @flowgram.ai/fixed-layout-editor @flowgram.ai/fixed-semi-materials",
+  "bun": "bun add @flowgram.ai/fixed-layout-editor @flowgram.ai/fixed-semi-materials",
+}} />
+
+2.  Install `styled-components` (if you haven't already):
+
+<PackageManagerTabs command={{
+  "npm": "npm install styled-components",
+  "pnpm": "pnpm add styled-components",
+  "yarn": "yarn add styled-components",
+  "bun": "bun add styled-components",
+}} />
+
+## Step 1: Import the Canvas Components
+
+1.  Import the stylesheet to ensure basic styles are applied:
+    ```tsx
+    import '@flowgram.ai/fixed-layout-editor/index.css';
+    ```
+
+2.  Use `FixedLayoutEditorProvider` to provide the editor context and `EditorRenderer` to render the canvas. Import the default Fixed Layout Semi component set via `materials.components`:
+    ```tsx
+    const FlowGramApp = () => (
+      <FixedLayoutEditorProvider
+        materials={{ components: defaultFixedSemiMaterials }}
+      >
+        <EditorRenderer />
+      </FixedLayoutEditorProvider>
+    );
+    ```
+
+3.  Keep the default exports for the remaining files.
+
+> **Expected Result:** After the page loads, only a blank canvas will be displayed, with no nodes or connections.
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step1
+}} />
+
+
+## Step 2: Implement the Node Component
+
+1.  Import the necessary APIs for node rendering:
+    *   `useNodeRender`: A hook to get the node context (e.g., drag, hover, highlight, form).
+    *   `FlowNodeEntity`: The type for a node entity, used to declare the props for `NodeRender`.
+
+2.  Create the `NodeRender` component, customize the node's size and style, and integrate drag-and-drop and form rendering:
+    ```tsx
+    import { useNodeRender, FlowNodeEntity } from '@flowgram.ai/fixed-layout-editor';
+
+    export const NodeRender = ({ node }: { node: FlowNodeEntity }) => {
+      const { onMouseEnter, onMouseLeave, startDrag, form, dragging, activated } = useNodeRender();
+      return (
+        <div
+          onMouseEnter={onMouseEnter}
+          onMouseLeave={onMouseLeave}
+          onMouseDown={(e) => { startDrag(e); e.stopPropagation(); }}
+          style={{ width: 280, minHeight: 88, background: '#fff', borderRadius: 8, opacity: dragging ? 0.3 : 1, /* ... */ }}
+        >
+          {form?.render()}
+        </div>
+      );
+    };
+    ```
+
+3.  Register the components in `FixedLayoutEditorProvider`:
+    *   `materials.renderDefaultNode`: Specifies `NodeRender` as the default node renderer.
+    *   `nodeRegistries`: Declares the available node types (e.g., `custom`).
+    *   `initialData`: Provides an initial node of type `custom`.
+
+> **Expected Result:** A draggable, custom-styled node will appear on the canvas.
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step2
+}} />
+
+
+## Step 3: Customize the "Add Node" Component and "Delete Node" Button
+
+1.  Add a delete button to the node:
+    *   In `NodeRender`, get the `ctx` using `useClientContext()`. Call `ctx.operation.deleteNode(node)` when the button is clicked to delete the current node.
+    *   Remember to stop event propagation with `e.stopPropagation()` to avoid interfering with canvas selection/drag behavior.
+    ```tsx
+    const ctx = useClientContext();
+    <button onClick={(e) => { e.stopPropagation(); ctx.operation.deleteNode(node); }}>×</button>
+    ```
+
+2.  Customize the "Add Node" component, `Adder`:
+    *   Use `useService(FlowOperationService)` and `usePlayground()` to encapsulate the `handleAdd` method: insert a new node after the specified node and scroll it into the center of the view.
+    *   Switch the UI based on `hoverActivated`: display a plus sign and a larger clickable area on hover; do not display in read-only mode.
+    ```tsx
+    const { handleAdd } = useAddNode();
+    const Adder = ({ from, hoverActivated }) => (
+      <div onClick={() => handleAdd({ type: 'custom', id: `custom_${Date.now()}` }, from)}>
+        {hoverActivated ? <span>+</span> : null}
+      </div>
+    );
+    ```
+
+3.  Register `Adder` in `materials.components`:
+    *   Override the default "Add Node" renderer using `FlowRendererKey.ADDER`.
+    ```tsx
+    materials={{
+      renderDefaultNode: NodeRender,
+      components: { ...defaultFixedSemiMaterials, [FlowRendererKey.ADDER]: Adder },
+    }}
+    ```
+
+4.  Initialize data and adapt the view:
+    *   `initialData` provides a basic flow: `start -> custom -> end`.
+    *   Call `fitView` in `onAllLayersRendered` to automatically fit the canvas content:
+    ```tsx
+    onAllLayersRendered={(ctx) => {
+      setTimeout(() => {
+        ctx.playground.config.fitView(ctx.document.root.bounds.pad(30));
+      }, 10);
+    }}
+    ```
+
+> **Expected Result:**
+>
+> *   The canvas will display a basic flow consisting of `start`, `custom`, and `end` nodes, with the view automatically centered/zoomed to a suitable range.
+> *   When hovering over an addable position, a circular plus button will appear. Clicking it will add a new `custom` node after the current node and automatically scroll to the new node.
+> *   A "×" delete button will be displayed in the top-right corner of each node. Clicking it will delete the node (the add component is hidden in read-only mode).
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step3
+}} />
+
+## Step 4: Introduce Plugins
+
+:::info
+*   `@flowgram.ai/minimap-plugin`: A minimap plugin that provides a small map view of the canvas.
+:::
+
+1.  Install the plugin dependency:
+
+<PackageManagerTabs command={{
+  "npm": "npm install @flowgram.ai/minimap-plugin",
+  "pnpm": "pnpm add @flowgram.ai/minimap-plugin",
+  "yarn": "yarn add @flowgram.ai/minimap-plugin",
+  "bun": "bun add @flowgram.ai/minimap-plugin",
+}} />
+
+2.  Import the plugin creation function from the corresponding package:
+    *   `createMinimapPlugin` is used to generate the canvas thumbnail.
+
+3.  Register the plugin in the `plugins` prop of `FixedLayoutEditorProvider`:
+    ```tsx
+    plugins={() => [
+      createMinimapPlugin({
+        enableDisplayAllNodes: true,
+      })
+    ]}
+    ```
+
+> **Expected Result:**
+>
+> *   A draggable/zoomable minimap will appear in the top-right corner of the canvas. Clicking or dragging the thumbnail allows for quick navigation of the main canvas.
+> *   With `enableDisplayAllNodes: true`, the minimap will display all nodes, making it easy to navigate long flows.
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step4
+}} />
+
+
+## Step 5: Split Files
+
+To prevent a single file from becoming too long, we need to split the editor configuration, node rendering, initial data, etc., which were originally in one component, into separate files. This facilitates maintenance, reuse, and collaboration.
+
+```sh
+- use-editor-props.tsx # Canvas configuration (centralized management of Provider's props)
+- node-render.tsx      # Node rendering (including delete button)
+- initial-data.ts      # Initial data (start/custom/end)
+- node-registries.tsx  # Node registration (example only registers 'custom')
+- adder.tsx            # Custom "Add Node" component (adds a custom node on click)
+- App.tsx              # Canvas entry point (mounts EditorRenderer)
+```
+
+**File Responsibilities:**
+
+*   `use-editor-props.tsx`: Centralizes all props for `FixedLayoutEditorProvider` (plugins, view adaptation, materials, node registration, and initial data):
+    *   `plugins`: Registers the minimap plugin `createMinimapPlugin({ enableDisplayAllNodes: true })`.
+    *   `onAllLayersRendered`: Calls `fitView` after rendering is complete to automatically fit the canvas content.
+    *   `materials`:
+        *   `renderDefaultNode`: Specifies `NodeRender` as the default node renderer.
+        *   `components`: Merges `defaultFixedSemiMaterials` and overrides the default adder with `[FlowRendererKey.ADDER]: Adder`.
+    *   `nodeRegistries` and `initialData` are imported from separate files.
+
+*   `node-render.tsx`: Defines the custom node renderer `NodeRender`, sets the node's appearance, and renders the internal form via `form?.render()`. It also provides a "×" delete button in the top-right corner (`useClientContext().operation.deleteNode(node)`).
+
+*   `initial-data.ts`: Provides the initial data for the basic flow, including `start -> custom -> end` nodes.
+
+*   `node-registries.tsx`: Declares the set of node types (the example only registers `'custom'`).
+
+*   `adder.tsx`: Implements the custom "Add Node" component `Adder`, which displays a plus sign on hover. On click, it adds a new `custom` node after the current node using `FlowOperationService.addFromNode` and calls `scrollToView` to automatically position the new node.
+
+*   `App.tsx`: The application entry point, which gets the configuration from `useEditorProps` and mounts `EditorRenderer`.
+
+> **Expected Result:** By splitting the files, the code structure becomes clearer and responsibilities are more defined, making future extensions and team collaboration easier. The UI effect is the same as in the previous section (basic flow, delete button, and "Add Node" component are available, with a minimap and automatic view adaptation).
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step5App,
+    '/use-editor-props.tsx': step5UseEditorProps,
+    '/initial-data.ts': step5InitialData,
+    '/node-registries.tsx': step5NodeRegistries,
+    '/node-render.tsx': step5NodeRender,
+    '/adder.tsx': step5Adder,
+}} />
+
+## Step 6: Integrate Forms and History
+
+1.  Node Registration and Extension:
+
+    *   `condition`: Extended as a "branch node" via `extend: 'dynamicSplit'`, and returns default `blocks` (multiple branches) in `onAdd()`.
+    *   `custom`: A normal node, with default `data.title` and `data.content` set in `onAdd()`.
+    *   The optional `meta` configuration item can control node behavior (e.g., draggable, selectable, deletable, copyable, addable).
+
+2.  Enable Forms and History:
+
+    In `use-editor-props.tsx`:
+    *   `nodeEngine.enable = true`: Enables the node engine, allowing `formMeta` to be configured for node types.
+    *   `history.enable = true` and `history.enableChangeNode = true`: Enable undo/redo and listen for node data changes (e.g., form field changes).
+    *   `history.onApply(ctx)`: Triggered after applying a history record, can be used for auto-saving (the example prints `ctx.document.toJSON()`).
+    *   `getNodeDefaultRegistry(type)`: Provides a default configuration for types that are not explicitly registered:
+        *   `meta.defaultExpanded = true`: The node's internal content area is expanded by default.
+        *   `formMeta.render`: Renders the form. This example uses `<Field<string> name="title">` and `<Field<string> name="content">` to display a title and an editable input box, respectively.
+        ```tsx
+        getNodeDefaultRegistry(type) {
+          return {
+            type,
+            meta: { defaultExpanded: true },
+            formMeta: {
+              render: () => (
+                <>
+                  <Field<string> name="title">{({ field }) => <div>{field.value}</div>}</Field>
+                  <Field<string> name="content">
+                    <input />
+                  </Field>
+                </>
+              ),
+            },
+          };
+        }
+        ```
+
+3.  Initialize Data and Rendering:
+
+    *   In `initial-data.ts`, include a `start` node, a `condition` node with three branches (one containing `custom`, another `break`, and one empty), and an `end` node.
+    *   Each node carries `data.title` and `data.content`. `form?.render()` in `NodeRender` will render these form fields inside the node's shell.
+    *   The `Adder` component adds a `custom` node on click, with the default `title: 'New Custom Node'` and `content: 'Custom Node Content'`.
+
+> **Expected Result:**
+>
+> *   The canvas contains `start`, `condition` (three branches), and `end` nodes. Each node displays its `title` and `content`. You can quickly add `custom` nodes to the flow using the `Adder`.
+> *   Undo/redo shortcuts are available. Node movement, addition, deletion, and form input changes are included in the history. The auto-save callback in the example will be executed when history is applied.
+> *   The expanded area of branch nodes is open by default, making it easy to view and edit internal content.
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step6App,
+    '/use-editor-props.tsx': step6UseEditorProps,
+    '/initial-data.ts': step6InitialData,
+    '/node-registries.tsx': step6NodeRegistries,
+    '/node-render.tsx': step6NodeRender,
+    '/adder.tsx': step6Adder,
+}} />
+
+## Step 7: Create a Toolbar
+
+1.  Import the Toolbar and Minimap Components:
+
+    *   In `App.tsx`, import and render `<Tools />` and a custom `<Minimap />` at the same level as `<EditorRenderer />` inside `FixedLayoutEditorProvider`. This allows them to access the editor context and canvas operation methods.
+
+2.  Control the Canvas with Tool Methods:
+
+    *   Use `usePlaygroundTools()` to get canvas operation methods: `zoomin/zoomout`, `fitView`, `changeLayout`, etc.
+    *   Display the zoom ratio in real-time: read the current canvas zoom from `tools.zoom` and display it as a percentage.
+
+3.  Integrate Undo/Redo Status:
+
+    *   Use `useClientContext()` to get `history` and listen to `history.undoRedoService.onChange` to update the availability of `Undo/Redo` in the toolbar.
+    *   Ensure history is enabled in `use-editor-props.tsx`: `history.enable = true` and `history.enableChangeNode = true`, so that undo/redo works for node data and layout changes.
+
+4.  Customize the Minimap (Optional):
+
+    *   Use `MinimapRender` and customize the container style to fix the thumbnail in the bottom-left corner, avoiding obstruction of the main interaction area.
+    *   In `use-editor-props.tsx`, pass `disableLayer: true` and `canvasStyle` (`canvasWidth/canvasHeight/canvasPadding`) to the minimap plugin to get a compact thumbnail size and margin.
+
+5.  Maintain Previous Functionality:
+
+    *   `NodeRender` continues to render form fields via `form?.render()` and provide a delete button. `Adder` supports one-click addition of `custom` nodes. The `condition/custom` type registration is maintained in `node-registries.tsx`. `initial-data.ts` contains the example flow `start → condition (three branches) → end`.
+
+> **Expected Result:**
+>
+> *   A toolbar appears in the bottom-left corner of the screen, supporting common operations like `ZoomIn/ZoomOut`, `FitView`, `ChangeLayout`, etc., and displaying the zoom ratio in real-time. The `Undo/Redo` buttons are updated based on the history state.
+> *   A custom-styled minimap is also displayed in the bottom-left corner. You can click/drag the thumbnail to quickly navigate the main canvas. Combined with the toolbar, this forms a complete editing tool area for more efficient operation.
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step7App,
+    '/use-editor-props.tsx': step7UseEditorProps,
+    '/initial-data.ts': step7InitialData,
+    '/node-registries.tsx': step7NodeRegistries,
+    '/node-render.tsx': step7NodeRender,
+    '/adder.tsx': step7Adder,
+    '/tools.tsx': step7Tools,
+    '/minimap.tsx': step7Minimap,
+}} />
+
+## Step 8: Learn More
+
+<div style={{
+  display: "grid",
+  gridTemplateColumns: "1fr 1fr",
+  gap: "2rem",
+  marginTop: "1rem",
+}}>
+  <div>
+  Learn more about Fixed Layout usage:
+  - [Load and Save](/guide/fixed-layout/load)
+  - [Nodes](/guide/fixed-layout/node)
+  - [Composite Nodes](/guide/fixed-layout/composite-nodes)
+  </div>
+  <div>
+  Learn more about FlowGram.AI features:
+  - [Forms](/guide/form/form)
+  - [Variables](/guide/variable/basic)
+  - [Materials](/materials/introduction)
+  </div>
+</div>

+ 302 - 0
apps/docs/src/en/guide/getting-started/free-layout.mdx

@@ -0,0 +1,302 @@
+# Free Layout
+
+import {
+  PackageManagerTabs
+  // @ts-ignore
+} from '@theme';
+import { CodePreview } from '@components/code-preview';
+import step1 from '@components/free-examples/step-1.tsx?raw';
+import step2 from '@components/free-examples/step-2.tsx?raw';
+import step3 from '@components/free-examples/step-3.tsx?raw';
+import step4 from '@components/free-examples/step-4.tsx?raw';
+import step5App from '@components/free-examples/step-5/app.tsx?raw';
+import step5InitialData from '@components/free-examples/step-5/initial-data.ts?raw';
+import step5UseEditorProps from '@components/free-examples/step-5/use-editor-props.tsx?raw';
+import step5NodeRender from '@components/free-examples/step-5/node-render.tsx?raw';
+import step5NodeRegistries from '@components/free-examples/step-5/node-registries.tsx?raw';
+import step6App from '@components/free-examples/step-6/app.tsx?raw';
+import step6InitialData from '@components/free-examples/step-6/initial-data.ts?raw';
+import step6UseEditorProps from '@components/free-examples/step-6/use-editor-props.tsx?raw';
+import step6NodeRender from '@components/free-examples/step-6/node-render.tsx?raw';
+import step6NodeRegistries from '@components/free-examples/step-6/node-registries.tsx?raw';
+import step7App from '@components/free-examples/step-7/app.tsx?raw';
+import step7InitialData from '@components/free-examples/step-7/initial-data.ts?raw';
+import step7UseEditorProps from '@components/free-examples/step-7/use-editor-props.tsx?raw';
+import step7NodeRender from '@components/free-examples/step-7/node-render.tsx?raw';
+import step7NodeRegistries from '@components/free-examples/step-7/node-registries.tsx?raw';
+import step7Tools from '@components/free-examples/step-7/tools.tsx?raw';
+import step7AddNode from '@components/free-examples/step-7/add-node.tsx?raw';
+import step7Minimap from '@components/free-examples/step-7/minimap.tsx?raw';
+
+## Step 0: Install Dependencies
+
+1. Install the editor package
+
+<PackageManagerTabs command={{
+  "npm": "npm install @flowgram.ai/free-layout-editor",
+  "pnpm": "pnpm add @flowgram.ai/free-layout-editor",
+  "yarn": "yarn add @flowgram.ai/free-layout-editor",
+  "bun": "bun add @flowgram.ai/free-layout-editor",
+}} />
+
+2. Install styled-components (if not already installed)
+
+<PackageManagerTabs command={{
+  "npm": "npm install styled-components",
+  "pnpm": "pnpm add styled-components",
+  "yarn": "yarn add styled-components",
+  "bun": "bun add styled-components",
+}} />
+
+## Step 1: Import the Canvas Component
+
+1. Import the stylesheet to ensure basic styles are applied:
+   ```tsx
+   import '@flowgram.ai/free-layout-editor/index.css';
+   ```
+
+2. Use `FreeLayoutEditorProvider` to provide the editor context, and `EditorRenderer` to render the canvas:
+   ```tsx
+   const FlowGramApp = () => (
+     <FreeLayoutEditorProvider>
+       <EditorRenderer />
+     </FreeLayoutEditorProvider>
+   );
+   ```
+
+3. The remaining files can keep their default exports.
+
+> Expected result: After the page loads, only a blank canvas is displayed, with no nodes or edges.
+
+<CodePreview files={{
+    '/App.tsx': step1
+}} />
+
+## Step 2: Implement and Register the Node Component
+
+1. Import hooks and components related to node rendering:
+   - `useNodeRender`: Gets the node context (such as the form).
+   - `WorkflowNodeProps` & `WorkflowNodeRenderer`: Define and render the node shell.
+
+2. Create the `NodeRender` component to customize node size and style:
+   ```tsx
+   const NodeRender = (props: WorkflowNodeProps) => {
+     const { form } = useNodeRender();
+     return (
+       <WorkflowNodeRenderer
+         style={{ width: 280, height: 88, background: '#fff', borderRadius: 8, ... }}
+         node={props.node}
+       >
+         {form?.render()}
+       </WorkflowNodeRenderer>
+     );
+   };
+   ```
+
+3. Register in `FreeLayoutEditorProvider`:
+   - `materials.renderDefaultNode` specifies the default node renderer.
+   - `nodeRegistries` declares available node types (e.g., `custom`).
+   - `initialData` provides an initial node at position `{ x: 250, y: 100 }`.
+
+> Expected result: A draggable, custom-styled node appears on the canvas.
+
+<CodePreview files={{
+    '/App.tsx': step2
+}} />
+
+## Step 3: Add Multiple Nodes and Edges
+
+1. Add the `onAllLayersRendered` callback. After all layers are rendered, call `ctx.tools.fitView(false)` to make the canvas automatically fit the content.
+
+2. Add `canDeleteNode` & `canDeleteLine` callbacks, returning `true` to allow deleting nodes and edges.
+
+3. Extend `initialData`:
+   - Add another node of the same type at position `{ x: 400, y: 0 }`.
+   - Add an edge in the `edges` array to connect node `1` and node `2`.
+
+> Expected result:
+>
+> • The canvas displays two connected nodes and automatically centers/zooms to fit the view.
+>
+> • Select any node or edge, and press the Delete key to remove it.
+
+<CodePreview files={{
+    '/App.tsx': step3
+}} />
+
+## Step 4: Import Plugins
+
+:::info
+
+- `@flowgram.ai/free-snap-plugin`: A node snapping plugin that aligns nodes to a grid.
+- `@flowgram.ai/minimap-plugin`: A minimap plugin that provides a small map view of the canvas.
+
+:::
+
+1. Install plugin dependencies
+
+<PackageManagerTabs command={{
+  "npm": "npm install @flowgram.ai/free-snap-plugin @flowgram.ai/minimap-plugin",
+  "pnpm": "pnpm add @flowgram.ai/free-snap-plugin @flowgram.ai/minimap-plugin",
+  "yarn": "yarn add @flowgram.ai/free-snap-plugin @flowgram.ai/minimap-plugin",
+  "bun": "bun add @flowgram.ai/free-snap-plugin @flowgram.ai/minimap-plugin",
+}} />
+
+2. Import the plugin creation functions from their respective packages:
+  - `createFreeSnapPlugin` is used for node grid snapping.
+  - `createMinimapPlugin` is used to generate the canvas minimap.
+
+3. Register the plugins in the `plugins` prop of `FreeLayoutEditorProvider`:
+  ```tsx
+  plugins={() => [
+    createMinimapPlugin({}),
+    createFreeSnapPlugin({})
+  ]}
+  ```
+
+> Expected result:
+>
+> • A draggable and zoomable minimap appears in the upper-right corner of the canvas. Clicking or dragging the minimap allows for quick navigation of the main canvas.
+>
+> • When dragging a node, it will automatically snap to nearby nodes for easy alignment.
+
+<CodePreview files={{
+    '/App.tsx': step4
+}} />
+
+
+## Step 5: Splitting Files
+
+To avoid having excessively long files, we need to split the editor configuration, node rendering, initial data, etc., which were originally in a single component, into separate files. This facilitates maintenance, reuse, and collaboration.
+
+```sh
+- use-editor-props.ts # Canvas configuration
+- node-render.tsx # Node rendering
+- initial-data.ts # Initial data
+- node-registries.ts # Node configuration
+- App.tsx # Canvas entry point
+```
+
+File Responsibilities
+
+- `use-editor-props.tsx`: Manages all props for FreeLayoutEditorProvider (plugins, view fitting, materials, node registration, and initial data).
+- `node-render.tsx`: Defines the custom node renderer NodeRender, responsible for its appearance and internal form rendering.
+- `initial-data.ts`: Provides the initial nodes and edges. The current example includes 5 `custom` nodes and multiple connections.
+- `node-registries.tsx`: Declares the set of node types (the example only registers 'custom').
+- `App.tsx`: The application entry point, which gets the configuration from useEditorProps and mounts EditorRenderer.
+
+> Expected result: By splitting the files, the code structure becomes clearer, responsibilities are more defined, and the code is easier to extend in the future.
+
+<CodePreview files={{
+    '/App.tsx': step5App,
+    '/use-editor-props.tsx': step5UseEditorProps,
+    '/initial-data.ts': step5InitialData,
+    '/node-registries.tsx': step5NodeRegistries,
+    '/node-render.tsx': step5NodeRender,
+}} />
+
+## Step 6: Integrating Forms and History
+
+1. Node Registration and Port Configuration
+
+- `start`: The starting node, which cannot be deleted and has only an output port by default.
+- `end`: The ending node, which cannot be deleted and has only an input port by default.
+- `custom`: A regular node, which has both input and output ports by default.
+
+2. Enable Forms and History
+
+In `useEditorProps.tsx`:
+- `nodeEngine.enable = true`: Enables the node engine, allowing `formMeta` to be configured for node types.
+- `history.enable = true` and `history.enableChangeNode = true`: Enable undo/redo and listen for node data changes (e.g., form changes).
+- `getNodeDefaultRegistry(type)`: Provides a default configuration for types that are not explicitly registered:
+  - `meta.defaultExpanded = true`: The node's internal content area is expanded by default.
+  - `formMeta.render`: Renders the form. This example renders the title field using `<Field<string> name="title">`.
+
+3. Initialize Data and Rendering
+
+- In `initial-data.ts`, set `data.title` for each node (e.g., `Start Node`, `Custom Node A/B/C`, `End Node`).
+- `form?.render()` in `NodeRender` will render the form content into the node shell, displaying the title of each node.
+
+> Expected result:
+>
+> • The canvas contains `start`, multiple `custom`, and `end` nodes, with connections matching the initial data.
+>
+> • Each node displays its `title`; selecting a node can expand it to show more form fields and interactions.
+>
+> • Undo/redo shortcuts are available and can be verified by deleting or moving nodes.
+
+<CodePreview activeFile="/use-editor-props.tsx" files={{
+    '/App.tsx': step6App,
+    '/use-editor-props.tsx': step6UseEditorProps,
+    '/initial-data.ts': step6InitialData,
+    '/node-registries.tsx': step6NodeRegistries,
+    '/node-render.tsx': step6NodeRender,
+}} />
+
+## Step 7: Creating a Toolbar
+
+1. Import the Toolbar Component
+
+- In `App.tsx`, import `<Tools />` and place it at the same level as `<EditorRenderer />` inside `FreeLayoutEditorProvider`, allowing it to access the editor context and tool methods.
+
+2. Control the Canvas with Tool Methods
+
+- Use `usePlaygroundTools()` to get canvas manipulation methods: `zoomin/zoomout`, `fitView`, `autoLayout`, `switchLineType`, etc.
+- Switch edge style: Use `switchLineType` to toggle between `LineType.BEZIER` and `LineType.LINE_CHART`.
+- Display real-time zoom ratio: Read `tools.zoom` to show the current canvas zoom percentage.
+
+3. Integrate Undo/Redo State
+
+- Use `useClientContext()` to get `history` and listen to `history.undoRedoService.onChange` to update the state of the `canUndo/canRedo` buttons.
+- In `use-editor-props.tsx`, ensure history is enabled: `history.enable = true` and `history.enableChangeNode = true`, so that undo/redo works for node data changes.
+
+5. Extend Components (Optional)
+
+- Minimap: Pin the minimap to the bottom-right corner by customizing the `MinimapRender` container style to improve navigation efficiency.
+- AddNode: Provides a button to quickly add a new node. It uses `WorkflowDocument.createWorkflowNodeByType` to create and select a node in the center of the canvas.
+
+> Expected result:
+>
+> • A toolbar appears in the bottom-right corner of the page, supporting common operations like Zoom In/Zoom Out, Fit View, Auto Layout, and displaying the real-time zoom ratio.
+>
+> • The edge style can be switched (Bezier/Polyline), and the undo/redo buttons are automatically enabled/disabled based on the history state.
+>
+> • Combined with the Minimap and AddNode components, it forms a complete editing tool area for more efficient operation.
+
+<CodePreview activeFile="/tools.tsx" files={{
+    '/App.tsx': step7App,
+    '/use-editor-props.tsx': step7UseEditorProps,
+    '/initial-data.ts': step7InitialData,
+    '/node-registries.tsx': step7NodeRegistries,
+    '/tools.tsx': step7Tools,
+    '/add-node.tsx': step7AddNode,
+    '/minimap.tsx': step7Minimap,
+    '/node-render.tsx': step7NodeRender,
+}} />
+
+## Step 8: Learn More
+
+Click to learn more about [Free Layout Usage](/guide/free-layout/load)
+
+<div style={{
+  display: "grid",
+  gridTemplateColumns: "1fr 1fr",
+  gap: "2rem",
+  marginTop: "1rem",
+}}>
+  <div>
+  Learn more about Free Layout:
+  - [Loading and Saving](/guide/free-layout/load)
+  - [Nodes](/guide/free-layout/node)
+  - [Lines](/guide/free-layout/line)
+  - [Ports](/guide/free-layout/port)
+  - [Sub-canvas](/guide/free-layout/sub-canvas)
+  </div>
+  <div>
+  Learn more about other FlowGram.AI features:
+  - [Form](/guide/form/form)
+  - [Variable](/guide/variable/basic)
+  - [Materials](/materials/introduction)
+  - [Runtime](/guide/runtime/introduction)
+  </div>
+</div>

+ 0 - 29
apps/docs/src/en/guide/getting-started/install.mdx

@@ -1,29 +0,0 @@
-# Installation
-
-import { PackageManagerTabs } from '@theme';
-
-## Install via npx
-
-<PackageManagerTabs command={{
-  npm: "npx @flowgram.ai/create-app@latest"
-}} />
-
-```shell
-# Select demo
-- fixed-layout # Best practices for fixed layout
-- free-layout # Best practices for free layout
-- fixed-layout-simple # Basic usage of fixed layout
-- free-layout-simple # Basic usage of free layout
-
-```
-
-## Install via npm
-
-<PackageManagerTabs command={{
-  npm: "npm install @flowgram.ai/fixed-layout-editor"
-}} />
-
-<PackageManagerTabs command={{
-  npm: "npm install @flowgram.ai/free-layout-editor"
-}} />
-

+ 160 - 0
apps/docs/src/en/guide/getting-started/introduction.mdx

@@ -0,0 +1,160 @@
+# Introduction
+
+FlowGram is a workflow development framework and toolkit. It helps developers build AI workflow platforms **faster** and **more easily**.
+FlowGram comes with a suite of built-in tools for workflow development: visual flow canvas, node configuration forms, variable scope chain, and ready-to-use materials.
+Use FlowGram to build your own AI workflow platform.
+
+## Why FlowGram
+
+FlowGram was originally designed to build diverse AI workflow platforms within ByteDance. These large-scale workflow platforms often have complex business logic and processes, and building them from scratch is not only time-consuming but also results in high development and maintenance costs.
+
+Many developers initially tried to use mainstream visual graphics libraries to build workflow platforms. However, these general-purpose libraries couldn't solve the core problems of workflow scenarios. Developers still had to handle a series of challenges themselves, such as node data management, dynamic forms, data validation, and variable scope chains. This led to low development efficiency and difficult long-term maintenance.
+
+To address these pain points, we introduced FlowGram, a development framework specifically designed for workflow scenarios, aiming to help developers improve the efficiency and shorten the development cycle of creating workflow platforms. FlowGram provides the following core features:
+
+- **Workflow Canvas**: Provides visual orchestration for nodes and edges, supporting both free and fixed layouts to easily build complex flowcharts.
+- **Forms**: The form engine manages the CRUD operations of node data and provides rendering, validation, side effects, linkage, and error-capturing capabilities, simplifying the development of node configurations.
+- **Variables**: The variable engine supports scope constraints, variable structure inspection, and type inference, making it easy to manage data flow within the workflow.
+- **Materials**: Offers out-of-the-box components, side effects, and validators that developers can quickly reuse and extend, boosting development efficiency.
+
+By combining these features, developers can focus on implementing business logic, thereby rapidly building full-featured, high-performance AI workflow platforms.
+
+## Next step
+
+Please read [Quick start](/guide/getting-started/quick-start) to start using FlowGram.
+
+Welcome to the [GitHub Discussions](https://github.com/bytedance/flowgram.ai/discussions) and [Discord](https://discord.com/invite/SwDWdrgA9f) to communicate with us.
+
+## Appendix: Interactive Experience
+
+FlowGram offers a suite of interactive features designed for a seamless and intuitive workflow-building experience.
+
+<table className="rs-table">
+  <tr>
+    <td>Smooth Motion Transitions</td>
+    <td>
+      <p>
+        FlowGram provides smooth motion transitions for all canvas elements. When nodes are resized or moved, animated transitions create a more natural and intuitive user experience. Our canvas engine is optimized for performance by rendering nodes and edges separately, making these animations fluid and efficient.
+      </p>
+      <div className="rs-center">
+        <img loading="lazy" src="/common/motion.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>Intuitive Canvas Navigation</td>
+    <td>
+      <p>
+        Navigate the canvas with familiar gestures inspired by professional design tools like Sketch and Figma. Use a two-finger pinch-to-zoom on your touchpad, or simply hold the spacebar and drag to pan across the canvas with ease.
+      </p>
+      <div className="rs-center">
+        <img loading="lazy" src="/common/touch-pad.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>Minimap</td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/minimap.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>Undo/Redo</td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/redo-undo.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>Copy/Paste (Shortcut Support)</td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/copypaste.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <div>
+        <div>Box Selection + Drag and Drop</div>
+        <div>(Fixed)</div>
+      </div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <div className="rs-center">
+          <img loading="lazy" src="/fixed-layout/dragdrop.gif" />
+        </div>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <div>Horizontal/Vertical Layout Switch</div>
+      <div>(Fixed)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/layout-change.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <div>Branch Folding</div>
+      <div>(Fixed)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/fold.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <div>Grouping</div>
+      <div>(Fixed)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/group.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      Auto Layout
+      <div>(Free)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/free-layout/autolayout.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      Snap Alignment + Guidelines
+      <div>(Free)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/free-layout/snap.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      Coze Loop Sub-canvas
+      <div>(Free)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/free-layout/loop.gif" />
+      </div>
+    </td>
+  </tr>
+</table>

+ 100 - 0
apps/docs/src/en/guide/getting-started/quick-start.mdx

@@ -0,0 +1,100 @@
+# Quick Start
+
+import {
+  PackageManagerTabs
+  // @ts-ignore
+} from '@theme';
+
+:::info
+To quickly experience FlowGram.AI, you can directly [open it in CodeSandbox](https://codesandbox.io/p/github/louisyoungx/flowgram-demo/main).
+:::
+
+Choose a way to start:
+- Option 1: Use the official template scaffolding to build a new project (⭐️ Recommended for a quick start).
+- Option 2: Integrate into an existing project by installing the editor package.
+
+## Option 1: Create a FlowGram.AI Application via the Official Template
+
+1. Use the FlowGram CLI to set up a runnable demo.
+
+<PackageManagerTabs command={{
+  npm: "npx @flowgram.ai/create-app@latest",
+  pnpm: "pnpm dlx @flowgram.ai/create-app@latest",
+  yarn: "yarn dlx @flowgram.ai/create-app@latest",
+  bun: "bunx @flowgram.ai/create-app@latest",
+}} />
+
+2. Select a template when prompted (it is recommended to choose `Free Layout Demo` for a quick start).
+
+```text
+- Free Layout Demo            # Best practice for free layout (⭐️ Recommended)
+- Free Layout Demo Simple     # Basic usage of free layout
+- Fixed Layout Demo           # Best practice for fixed layout
+- Fixed Layout Demo Simple    # Basic usage of fixed layout
+```
+
+<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(320px, 1fr))', gap: 16, marginTop: 12 }}>
+  <div>
+    <p><strong>Free Layout Demo</strong> [View Online Demo](/examples/free-layout/free-layout-simple.html)</p>
+    <img src="/examples/example-free-layout.png" alt="Free Layout Preview" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+  <div>
+    <p><strong>Fixed Layout Demo</strong> [View Online Demo](/examples/fixed-layout/fixed-layout-simple.html)</p>
+    <img src="/examples/example-fixed-layout.png" alt="Fixed Layout Preview" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+  <div>
+    <p><strong>Free Layout Demo Simple</strong> [View Online Demo](/examples/free-layout/free-layout-simple.html)</p>
+    <img src="/examples/example-free-layout-simple.png" alt="Free Layout Simple Preview" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+  <div>
+    <p><strong>Fixed Layout Demo Simple</strong> [View Online Demo](/examples/fixed-layout/fixed-layout-simple.html)</p>
+    <img src="/examples/example-fixed-layout-simple.png" alt="Fixed Layout Simple Preview" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+</div>
+
+3. Check the installed directory name.
+
+- For a project created with the Free Layout Demo template, the directory name is `demo-free-layout`.
+- For a project created with the Free Layout Demo Simple template, the directory name is `demo-free-layout-simple`.
+- For a project created with the Fixed Layout Demo template, the directory name is `demo-fixed-layout`.
+- For a project created with the Fixed Layout Demo Simple template, the directory name is `demo-fixed-layout-simple`.
+
+4. Enter the project directory.
+
+```sh
+cd [project-name]
+```
+
+5. Start the development server.
+
+<PackageManagerTabs command={{
+  npm: "npm run dev",
+  pnpm: "pnpm dev",
+  yarn: "yarn dev",
+  bun: "bun dev",
+}} />
+
+## Option 2: Install the Editor Package Directly
+
+:::tip
+This method is suitable for developers who have some familiarity with the FlowGram project.
+
+If you are new to FlowGram, we recommend choosing Option 1 first to familiarize yourself with the project, and then gradually integrate the required code into your existing project.
+:::
+
+If you need to add the package to an existing project, choose a layout type:
+
+<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
+  <div>
+    <strong>Free Layout</strong>
+    <p>Nodes can be dragged freely on the canvas, and edges can be used to connect nodes to establish logical relationships between them.</p>
+    <p>Next: [Create a Free Layout Canvas](/guide/getting-started/free-layout)</p>
+    <img src="/free-layout/free-layout-demo.gif" alt="Free Layout Demo" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+  <div>
+    <strong>Fixed Layout</strong>
+    <p>The position of nodes in the graph represents the logical relationship between them.</p>
+    <p>Next: [Create a Fixed Layout Canvas](/guide/getting-started/fixed-layout)</p>
+    <img src="/fixed-layout/fixed-layout-demo.gif" alt="Fixed Layout Demo" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+</div>

+ 0 - 248
apps/docs/src/en/guide/introduction.mdx

@@ -1,248 +0,0 @@
-# Introduction
-
-FlowGram is a node-based flow building engine that helps developers quickly create workflows in either fixed layout or free connection layout modes, providing a set of interactive best practices. It's particularly suitable for visual workflows with clear inputs and outputs.
-
-In today's AI-driven era, we are focusing more on how to empower workflows with AI, hence the AI suffix in our name.
-
-<div className="rs-highlight">
-  FlowGram = Flow + Program, suggesting that flows are like programs, with
-  Condition, Loop, and even TryCatch nodes.
-</div>
-
-## Official Demo
-
-<div style={{ marginTop: 16, display: "flex", gap: 8 }}>
-  <div>
-    <div>
-      <a
-        className="rs-link"
-        href="/en/examples/fixed-layout/fixed-feature-overview.html"
-      >
-        Fixed Layout
-      </a>
-    </div>
-    <div className="rs-tip" style={{ height: 84 }}>
-      Fixed layout with nodes/branches supporting specified position drag and
-      drop, offering compound nodes like branches and loops
-    </div>
-    <div>
-      <img loading="lazy" src="/fixed-layout/fixed-layout-demo.gif" />
-    </div>
-  </div>
-  <div>
-    <div>
-      <a
-        className="rs-link"
-        href="/en/examples/free-layout/free-feature-overview.html"
-      >
-        Free Connection Layout
-      </a>
-    </div>
-    <div className="rs-tip" style={{ height: 84 }}>
-      Free layout where nodes can be moved to any position and connected through
-      free connections
-    </div>
-    <div>
-      <img loading="lazy" src="/free-layout/free-layout-demo.gif" />
-    </div>
-  </div>
-</div>
-
-## Interactive Experience
-
-Providing a set of interactive best practices for smoother workflow operations
-
-<table className="rs-table">
-  <tr>
-    <td>Motion Transitions</td>
-    <td>
-      <p>
-        Motion animations in web applications can be traced back to Material
-        Design, which suggests that element changes in width, height, or
-        position need a transition process. The canvas engine separates the
-        drawing of lines and nodes, greatly reducing the cost of implementing
-        motion transitions
-      </p>
-      <div className="rs-center">
-        <img loading="lazy" src="/common/motion.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>Touchpad Gesture Zoom + Space Key Canvas Drag</td>
-    <td>
-      <p>
-        Gestures refer to Mac touchpad two-finger spread/pinch for canvas zoom
-        in/out, or holding space to drag the canvas, interactions inspired by
-        Sketch and Figma
-      </p>
-      <div className="rs-center">
-        <img loading="lazy" src="/common/touch-pad.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>Minimap</td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/minimap.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>Undo/Redo</td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/redo-undo.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>Copy/Paste (Shortcut Support)</td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/copypaste.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      <div>
-        <div>Box Selection + Drag and Drop</div>
-        <div>(Fixed)</div>
-      </div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <div className="rs-center">
-          <img loading="lazy" src="/fixed-layout/dragdrop.gif" />
-        </div>
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      <div>Horizontal/Vertical Layout Switch</div>
-      <div>(Fixed)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/layout-change.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      <div>Branch Folding</div>
-      <div>(Fixed)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/fold.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      <div>Grouping</div>
-      <div>(Fixed)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/group.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      Auto Layout
-      <div>(Free)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/free-layout/autolayout.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      Snap Alignment + Guidelines
-      <div>(Free)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/free-layout/snap.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      Coze Loop Sub-canvas
-      <div>(Free)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/free-layout/loop.gif" />
-      </div>
-    </td>
-  </tr>
-</table>
-
-## Online Applications
-
-<div style={{ marginTop: 16, display: "flex", gap: 8, flexWrap: 'wrap' }}>
-  <div style={{flex: '0 0 30%', boxSizing: 'border-box'}}>
-    <div>
-      <a
-        className="rs-link"
-        href="https://www.coze.com/open/docs/guides/workflow"
-        target="_blank"
-      >
-        Coze Workflow
-      </a>
-    </div>
-    <div>
-      <img loading="lazy" src="/ref-coze-en.png" />
-    </div>
-  </div>
-  <div style={{flex: '0 0 30%', boxSizing: 'border-box'}}>
-    <a
-      className="rs-link"
-      href="https://ae.feishu.cn/hc/zh-CN/articles/120610822514"
-      target="_blank"
-    >
-      Lark Low-code Platform Workflow
-    </a>
-    <div>
-      <img loading="lazy" src="/ref-apaas-en.png" />
-    </div>
-  </div>
-  <div style={{flex: '0 0 30%', boxSizing: 'border-box'}}>
-    <a
-      className="rs-link"
-      href="https://www.feishu.cn/hc/en-US/articles/908751305974-overview-of-workflow-in-base"
-      target="_blank"
-    >
-      Lark Base Workflow
-    </a>
-    <div>
-      <img loading="lazy" src="/ref-bitable-en.png" />
-    </div>
-  </div>
-  <div style={{flex: '0 0 30%', boxSizing: 'border-box'}}>
-    <a className="rs-link" href="https://github.com/NNDeploy/nndeploy" target="_blank" >
-      nndeploy
-    </a>
-    <div>
-      <img loading="lazy" src="/ref-nndeploy.png"/>
-    </div>
-  </div>
-  <div style={{flex: '0 0 30%', boxSizing: 'border-box'}}>
-    <a className="rs-link" href="https://github.com/certimate-go/certimate" target="_blank" >
-      Certimate
-    </a>
-    <div>
-      <img loading="lazy" src="/ref-certimate.png"/>
-    </div>
-  </div>
-</div>

+ 4 - 4
apps/docs/src/en/index.md

@@ -3,17 +3,17 @@ pageType: home
 
 hero:
   name: FlowGram.AI
-  text: Build Flow Engine with Plugins
-  tagline: High Performance, Scalable, Customizable
+  text: Workflow development framework
+  tagline: Building workflow platforms easily - Canvas, Form, Variable, Materials
   actions:
     - theme: brand
       text: Quick Start
-      link: /guide/introduction
+      link: /guide/getting-started/introduction
     - theme: alt
       text: GitHub
       link: https://github.com/bytedance/flowgram.ai
   image:
-    src: /logo.png
+    src: /transparent-logo.svg
     alt: Logo
 features:
   - title: Coze

BIN
apps/docs/src/public/examples/example-fixed-layout-simple.png


BIN
apps/docs/src/public/examples/example-fixed-layout.png


BIN
apps/docs/src/public/examples/example-free-layout-simple.png


BIN
apps/docs/src/public/examples/example-free-layout.png


+ 56 - 0
apps/docs/src/public/flowgram-avatar.svg

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated by Pixelmator Pro 3.7 -->
+<svg width="4096" height="4096" viewBox="0 0 4096 4096" xmlns="http://www.w3.org/2000/svg">
+    <g id="g1">
+        <g id="g2">
+            <g id="--3">
+                <path id="path1" fill="#4161a6" fill-rule="evenodd" stroke="none" d="M 742.253296 2741.590332 C 742.253296 2809.586914 687.131165 2864.708984 619.134705 2864.708984 L 320.118561 2864.708984 C 252.12207 2864.708984 197 2809.586914 197 2741.590332 L 197 1352.068115 C 197 1284.071533 252.12207 1228.949463 320.118561 1228.949463 L 619.134705 1228.949463 C 687.131165 1228.949463 742.253296 1284.071533 742.253296 1352.068115 L 742.253296 1918.409912 C 741.134827 1918.381348 740.012817 1918.366943 738.887451 1918.366943 C 667.321411 1918.366943 609.305664 1976.382568 609.305664 2047.94873 C 609.305664 2119.514648 667.321411 2177.530273 738.887451 2177.530273 C 740.012817 2177.530273 741.134827 2177.516113 742.253296 2177.487549 L 742.253296 2741.590332 Z"/>
+                <g id="-">
+                    <path id="Circle--2" fill="#4161a6" fill-rule="evenodd" stroke="none" d="M 738.887451 1961.560791 C 691.176758 1961.560791 652.499634 2000.238037 652.499634 2047.94873 C 652.499634 2095.65918 691.176758 2134.336426 738.887451 2134.336426 C 786.598145 2134.336426 825.27533 2095.65918 825.27533 2047.94873 C 825.27533 2000.238037 786.598145 1961.560791 738.887451 1961.560791 Z"/>
+                </g>
+            </g>
+            <g id="--4">
+                <g id="g3">
+                    <path id="path2" fill="#5681bd" fill-rule="evenodd" stroke="none" d="M 1795.231445 1962.907227 C 1747.520752 1962.907227 1708.843506 2001.584229 1708.843506 2049.294922 C 1708.843506 2097.005615 1747.520752 2135.682861 1795.231445 2135.682861 C 1842.942139 2135.682861 1881.619263 2097.005615 1881.619263 2049.294922 C 1881.619263 2001.584229 1842.942139 1962.907227 1795.231445 1962.907227 Z"/>
+                </g>
+                <g id="--2">
+                    <path id="path3" fill="#5681bd" fill-rule="evenodd" stroke="none" d="M 1675.478516 2864.708984 C 1743.475098 2864.708984 1798.597046 2809.586914 1798.597046 2741.590332 L 1798.597046 2178.833984 C 1797.478638 2178.862305 1796.356689 2178.876709 1795.231445 2178.876709 C 1723.665405 2178.876709 1665.649536 2120.86084 1665.649536 2049.294922 C 1665.649536 1977.729004 1723.665405 1919.713135 1795.231445 1919.713135 C 1796.356689 1919.713135 1797.478638 1919.727539 1798.597046 1919.756104 L 1798.597046 1352.068115 C 1798.597046 1284.071533 1743.475098 1228.949463 1675.478516 1228.949463 L 1376.462524 1228.949463 C 1308.466064 1228.949463 1253.343994 1284.071533 1253.343994 1352.068115 L 1253.343994 1924.334961 C 1322.805786 1926.72876 1378.381958 1983.793457 1378.381958 2053.838867 C 1378.381958 2123.883789 1322.805786 2180.94873 1253.343994 2183.342285 L 1253.343994 2741.590332 C 1253.343994 2809.586914 1308.466064 2864.708984 1376.462524 2864.708984 L 1675.478516 2864.708984 Z"/>
+                    <path id="path4" fill="#5681bd" fill-rule="evenodd" stroke="none" d="M 1248.800171 1967.450928 C 1201.089478 1967.450928 1162.412354 2006.12793 1162.412354 2053.838867 C 1162.412354 2101.549316 1201.089478 2140.226563 1248.800171 2140.226563 C 1296.510864 2140.226563 1335.187988 2101.549316 1335.187988 2053.838867 C 1335.187988 2006.12793 1296.510864 1967.450928 1248.800171 1967.450928 Z"/>
+                </g>
+            </g>
+            <g id="--5">
+                <g id="g4">
+                    <path id="path5" fill="#5dbbc1" fill-rule="evenodd" stroke="none" d="M 2847.368164 1965.094971 C 2799.657471 1965.094971 2760.980225 2003.771973 2760.980225 2051.482666 C 2760.980225 2099.193359 2799.657471 2137.870605 2847.368164 2137.870605 C 2895.078857 2137.870605 2933.755859 2099.193359 2933.755859 2051.482666 C 2933.755859 2003.771973 2895.078857 1965.094971 2847.368164 1965.094971 Z"/>
+                </g>
+                <g id="g5">
+                    <path id="path6" fill="#5dbbc1" fill-rule="evenodd" stroke="none" d="M 2727.615479 2867.906494 C 2795.611816 2867.906494 2850.733887 2812.784424 2850.733887 2744.788086 L 2850.733887 2181.021484 C 2849.615479 2181.050293 2848.493408 2181.064453 2847.368164 2181.064453 C 2775.802002 2181.064453 2717.786377 2123.048828 2717.786377 2051.482666 C 2717.786377 1979.916748 2775.802002 1921.900879 2847.368164 1921.900879 C 2848.493408 1921.900879 2849.615479 1921.915283 2850.733887 1921.943848 L 2850.733887 1355.265381 C 2850.733887 1287.268799 2795.611816 1232.146729 2727.615479 1232.146729 L 2428.599121 1232.146729 C 2360.602539 1232.146729 2305.480469 1287.268799 2305.480469 1355.265381 L 2305.480469 1921.979004 C 2374.942383 1924.372559 2430.518799 1981.4375 2430.518799 2051.482666 C 2430.518799 2121.527832 2374.942383 2178.592773 2305.480469 2180.986328 L 2305.480469 2744.788086 C 2305.480469 2812.784424 2360.602539 2867.906494 2428.599121 2867.906494 L 2727.615479 2867.906494 Z"/>
+                    <path id="path7" fill="#5dbbc1" fill-rule="evenodd" stroke="none" d="M 2300.937012 1965.094971 C 2253.226318 1965.094971 2214.549072 2003.771973 2214.549072 2051.482666 C 2214.549072 2099.193359 2253.226318 2137.870605 2300.937012 2137.870605 C 2348.647705 2137.870605 2387.324707 2099.193359 2387.324707 2051.482666 C 2387.324707 2003.771973 2348.647705 1965.094971 2300.937012 1965.094971 Z"/>
+                </g>
+            </g>
+            <g id="--6">
+                <g id="g6">
+                    <path id="path8" fill="#8bc9c5" fill-rule="evenodd" stroke="none" d="M 3899 2742.263672 C 3899 2810.260254 3843.87793 2865.382324 3775.881348 2865.382324 L 3476.865479 2865.382324 C 3408.868896 2865.382324 3353.746826 2810.260254 3353.746826 2742.263672 L 3353.746826 2177.530273 C 3425.312744 2177.530273 3483.328613 2119.514648 3483.328613 2047.94873 C 3483.328613 1976.382568 3425.312744 1918.366943 3353.746826 1918.366943 L 3353.746826 1352.741211 C 3353.746826 1284.744629 3408.868896 1229.622559 3476.865479 1229.622559 L 3775.881348 1229.622559 C 3843.87793 1229.622559 3899 1284.744629 3899 1352.741211 L 3899 2742.263672 Z"/>
+                    <path id="path9" fill="#8bc9c5" fill-rule="evenodd" stroke="none" d="M 3353.746826 1961.560791 C 3306.036133 1961.560791 3267.358887 2000.238037 3267.358887 2047.94873 C 3267.358887 2095.65918 3306.036133 2134.336426 3353.746826 2134.336426 C 3401.45752 2134.336426 3440.134766 2095.65918 3440.134766 2047.94873 C 3440.134766 2000.238037 3401.45752 1961.560791 3353.746826 1961.560791 Z"/>
+                </g>
+            </g>
+            <g id="g7">
+                <linearGradient id="linearGradient1" x1="737.709453" y1="2046.829299" x2="1251.156218" y2="2046.829299" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 -1e-06 1e-06 1 -0.002001 0.000972)">
+                    <stop offset="0" stop-color="#4161a6" stop-opacity="1"/>
+                    <stop offset="1" stop-color="#5681bd" stop-opacity="1"/>
+                </linearGradient>
+                <path id="path10" fill="none" stroke="url(#linearGradient1)" stroke-width="34.696575" d="M 737.709473 2046.82959 L 1251.15625 2046.829102"/>
+                <linearGradient id="linearGradient2" x1="1796.409415" y1="2046.829299" x2="2309.85618" y2="2046.829299" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 -1e-06 1e-06 1 -0.002001 0.002007)">
+                    <stop offset="0" stop-color="#5681bd" stop-opacity="1"/>
+                    <stop offset="1" stop-color="#5dbbc1" stop-opacity="1"/>
+                </linearGradient>
+                <path id="path11" fill="none" stroke="url(#linearGradient2)" stroke-width="34.696575" d="M 1796.409424 2046.82959 L 2309.856201 2046.829102"/>
+                <linearGradient id="linearGradient3" x1="2846.021824" y1="2051.373076" x2="3359.46859" y2="2051.373076" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 -1e-06 1e-06 1 -0.002006 0.003033)">
+                    <stop offset="0" stop-color="#5dbbc1" stop-opacity="1"/>
+                    <stop offset="1" stop-color="#8bc9c5" stop-opacity="1"/>
+                </linearGradient>
+                <path id="path12" fill="none" stroke="url(#linearGradient3)" stroke-width="34.696575" d="M 2846.021729 2051.373291 L 3359.468506 2051.372803"/>
+            </g>
+        </g>
+        <text id="F--L-" xml:space="preserve" x="716" y="3464" font-family="PingFang SC" font-size="249.924048" font-weight="600" fill="#4161a6">F  L  O  W  G  R  A  M</text>
+    </g>
+</svg>

+ 55 - 0
apps/docs/src/public/flowgram-logo.svg

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated by Pixelmator Pro 3.7 -->
+<svg width="4096" height="4096" viewBox="0 0 4096 4096" xmlns="http://www.w3.org/2000/svg">
+    <g id="g1">
+        <g id="g2">
+            <g id="--3">
+                <path id="path1" fill="#4161a6" fill-rule="evenodd" stroke="none" d="M 742.253296 2741.590332 C 742.253296 2809.586914 687.131165 2864.708984 619.134705 2864.708984 L 320.118561 2864.708984 C 252.12207 2864.708984 197 2809.586914 197 2741.590332 L 197 1352.068115 C 197 1284.071533 252.12207 1228.949463 320.118561 1228.949463 L 619.134705 1228.949463 C 687.131165 1228.949463 742.253296 1284.071533 742.253296 1352.068115 L 742.253296 1918.409912 C 741.134827 1918.381348 740.012817 1918.366943 738.887451 1918.366943 C 667.321411 1918.366943 609.305664 1976.382568 609.305664 2047.94873 C 609.305664 2119.514648 667.321411 2177.530273 738.887451 2177.530273 C 740.012817 2177.530273 741.134827 2177.516113 742.253296 2177.487549 L 742.253296 2741.590332 Z"/>
+                <g id="-">
+                    <path id="Circle--2" fill="#4161a6" fill-rule="evenodd" stroke="none" d="M 738.887451 1961.560791 C 691.176758 1961.560791 652.499634 2000.238037 652.499634 2047.94873 C 652.499634 2095.65918 691.176758 2134.336426 738.887451 2134.336426 C 786.598145 2134.336426 825.27533 2095.65918 825.27533 2047.94873 C 825.27533 2000.238037 786.598145 1961.560791 738.887451 1961.560791 Z"/>
+                </g>
+            </g>
+            <g id="--4">
+                <g id="g3">
+                    <path id="path2" fill="#5681bd" fill-rule="evenodd" stroke="none" d="M 1795.231445 1962.907227 C 1747.520752 1962.907227 1708.843506 2001.584229 1708.843506 2049.294922 C 1708.843506 2097.005615 1747.520752 2135.682861 1795.231445 2135.682861 C 1842.942139 2135.682861 1881.619263 2097.005615 1881.619263 2049.294922 C 1881.619263 2001.584229 1842.942139 1962.907227 1795.231445 1962.907227 Z"/>
+                </g>
+                <g id="--2">
+                    <path id="path3" fill="#5681bd" fill-rule="evenodd" stroke="none" d="M 1675.478516 2864.708984 C 1743.475098 2864.708984 1798.597046 2809.586914 1798.597046 2741.590332 L 1798.597046 2178.833984 C 1797.478638 2178.862305 1796.356689 2178.876709 1795.231445 2178.876709 C 1723.665405 2178.876709 1665.649536 2120.86084 1665.649536 2049.294922 C 1665.649536 1977.729004 1723.665405 1919.713135 1795.231445 1919.713135 C 1796.356689 1919.713135 1797.478638 1919.727539 1798.597046 1919.756104 L 1798.597046 1352.068115 C 1798.597046 1284.071533 1743.475098 1228.949463 1675.478516 1228.949463 L 1376.462524 1228.949463 C 1308.466064 1228.949463 1253.343994 1284.071533 1253.343994 1352.068115 L 1253.343994 1924.334961 C 1322.805786 1926.72876 1378.381958 1983.793457 1378.381958 2053.838867 C 1378.381958 2123.883789 1322.805786 2180.94873 1253.343994 2183.342285 L 1253.343994 2741.590332 C 1253.343994 2809.586914 1308.466064 2864.708984 1376.462524 2864.708984 L 1675.478516 2864.708984 Z"/>
+                    <path id="path4" fill="#5681bd" fill-rule="evenodd" stroke="none" d="M 1248.800171 1967.450928 C 1201.089478 1967.450928 1162.412354 2006.12793 1162.412354 2053.838867 C 1162.412354 2101.549316 1201.089478 2140.226563 1248.800171 2140.226563 C 1296.510864 2140.226563 1335.187988 2101.549316 1335.187988 2053.838867 C 1335.187988 2006.12793 1296.510864 1967.450928 1248.800171 1967.450928 Z"/>
+                </g>
+            </g>
+            <g id="--5">
+                <g id="g4">
+                    <path id="path5" fill="#5dbbc1" fill-rule="evenodd" stroke="none" d="M 2847.368164 1965.094971 C 2799.657471 1965.094971 2760.980225 2003.771973 2760.980225 2051.482666 C 2760.980225 2099.193359 2799.657471 2137.870605 2847.368164 2137.870605 C 2895.078857 2137.870605 2933.755859 2099.193359 2933.755859 2051.482666 C 2933.755859 2003.771973 2895.078857 1965.094971 2847.368164 1965.094971 Z"/>
+                </g>
+                <g id="g5">
+                    <path id="path6" fill="#5dbbc1" fill-rule="evenodd" stroke="none" d="M 2727.615479 2867.906494 C 2795.611816 2867.906494 2850.733887 2812.784424 2850.733887 2744.788086 L 2850.733887 2181.021484 C 2849.615479 2181.050293 2848.493408 2181.064453 2847.368164 2181.064453 C 2775.802002 2181.064453 2717.786377 2123.048828 2717.786377 2051.482666 C 2717.786377 1979.916748 2775.802002 1921.900879 2847.368164 1921.900879 C 2848.493408 1921.900879 2849.615479 1921.915283 2850.733887 1921.943848 L 2850.733887 1355.265381 C 2850.733887 1287.268799 2795.611816 1232.146729 2727.615479 1232.146729 L 2428.599121 1232.146729 C 2360.602539 1232.146729 2305.480469 1287.268799 2305.480469 1355.265381 L 2305.480469 1921.979004 C 2374.942383 1924.372559 2430.518799 1981.4375 2430.518799 2051.482666 C 2430.518799 2121.527832 2374.942383 2178.592773 2305.480469 2180.986328 L 2305.480469 2744.788086 C 2305.480469 2812.784424 2360.602539 2867.906494 2428.599121 2867.906494 L 2727.615479 2867.906494 Z"/>
+                    <path id="path7" fill="#5dbbc1" fill-rule="evenodd" stroke="none" d="M 2300.937012 1965.094971 C 2253.226318 1965.094971 2214.549072 2003.771973 2214.549072 2051.482666 C 2214.549072 2099.193359 2253.226318 2137.870605 2300.937012 2137.870605 C 2348.647705 2137.870605 2387.324707 2099.193359 2387.324707 2051.482666 C 2387.324707 2003.771973 2348.647705 1965.094971 2300.937012 1965.094971 Z"/>
+                </g>
+            </g>
+            <g id="--6">
+                <g id="g6">
+                    <path id="path8" fill="#8bc9c5" fill-rule="evenodd" stroke="none" d="M 3899 2742.263672 C 3899 2810.260254 3843.87793 2865.382324 3775.881348 2865.382324 L 3476.865479 2865.382324 C 3408.868896 2865.382324 3353.746826 2810.260254 3353.746826 2742.263672 L 3353.746826 2177.530273 C 3425.312744 2177.530273 3483.328613 2119.514648 3483.328613 2047.94873 C 3483.328613 1976.382568 3425.312744 1918.366943 3353.746826 1918.366943 L 3353.746826 1352.741211 C 3353.746826 1284.744629 3408.868896 1229.622559 3476.865479 1229.622559 L 3775.881348 1229.622559 C 3843.87793 1229.622559 3899 1284.744629 3899 1352.741211 L 3899 2742.263672 Z"/>
+                    <path id="path9" fill="#8bc9c5" fill-rule="evenodd" stroke="none" d="M 3353.746826 1961.560791 C 3306.036133 1961.560791 3267.358887 2000.238037 3267.358887 2047.94873 C 3267.358887 2095.65918 3306.036133 2134.336426 3353.746826 2134.336426 C 3401.45752 2134.336426 3440.134766 2095.65918 3440.134766 2047.94873 C 3440.134766 2000.238037 3401.45752 1961.560791 3353.746826 1961.560791 Z"/>
+                </g>
+            </g>
+            <g id="g7">
+                <linearGradient id="linearGradient1" x1="737.709453" y1="2046.829299" x2="1251.156218" y2="2046.829299" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 -1e-06 1e-06 1 -0.002001 0.000972)">
+                    <stop offset="0" stop-color="#4161a6" stop-opacity="1"/>
+                    <stop offset="1" stop-color="#5681bd" stop-opacity="1"/>
+                </linearGradient>
+                <path id="path10" fill="none" stroke="url(#linearGradient1)" stroke-width="34.696575" d="M 737.709473 2046.82959 L 1251.15625 2046.829102"/>
+                <linearGradient id="linearGradient2" x1="1796.409415" y1="2046.829299" x2="2309.85618" y2="2046.829299" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 -1e-06 1e-06 1 -0.002001 0.002007)">
+                    <stop offset="0" stop-color="#5681bd" stop-opacity="1"/>
+                    <stop offset="1" stop-color="#5dbbc1" stop-opacity="1"/>
+                </linearGradient>
+                <path id="path11" fill="none" stroke="url(#linearGradient2)" stroke-width="34.696575" d="M 1796.409424 2046.82959 L 2309.856201 2046.829102"/>
+                <linearGradient id="linearGradient3" x1="2846.021824" y1="2051.373076" x2="3359.46859" y2="2051.373076" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 -1e-06 1e-06 1 -0.002006 0.003033)">
+                    <stop offset="0" stop-color="#5dbbc1" stop-opacity="1"/>
+                    <stop offset="1" stop-color="#8bc9c5" stop-opacity="1"/>
+                </linearGradient>
+                <path id="path12" fill="none" stroke="url(#linearGradient3)" stroke-width="34.696575" d="M 2846.021729 2051.373291 L 3359.468506 2051.372803"/>
+            </g>
+        </g>
+    </g>
+</svg>

+ 3 - 0
apps/docs/src/public/transparent-logo.svg

@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated by Pixelmator Pro 3.7 -->
+<svg width="4096" height="4096" viewBox="0 0 4096 4096" xmlns="http://www.w3.org/2000/svg"/>

+ 1 - 1
apps/docs/src/zh/_nav.json

@@ -1,7 +1,7 @@
 [
   {
     "text": "指引",
-    "link": "/guide/introduction",
+    "link": "/guide/getting-started/introduction",
     "activeMatch": "/guide/"
   },
   {

+ 5 - 5
apps/docs/src/zh/guide/_meta.json

@@ -1,10 +1,8 @@
 [
-  "introduction",
-  "contact-us",
   {
     "type": "dir",
     "name": "getting-started",
-    "label": "快速开始"
+    "label": "开始"
   },
   {
     "type": "dir",
@@ -44,6 +42,8 @@
   {
     "type": "dir",
     "name": "runtime",
-    "label": "运行时接入 (WIP)"
-  }
+    "label": "运行时接入"
+  },
+  "contributing",
+  "contact-us"
 ]

+ 112 - 0
apps/docs/src/zh/guide/contributing.mdx

@@ -0,0 +1,112 @@
+# 贡献指南
+
+本文帮助你快速在本仓库完成开发、测试与提 PR。仓库采用 Rush + pnpm 的 Monorepo 管理方式,文档站基于 Rspress 构建。
+
+## 构建开发环境
+
+1. **安装 Node.js 18+**(推荐 LTS/Hydrogen)
+
+```bash
+nvm install lts/hydrogen
+nvm alias default lts/hydrogen # 设为默认 Node 版本
+nvm use lts/hydrogen
+```
+
+2. **克隆仓库到本地**
+
+```bash
+git clone git@github.com:bytedance/flowgram.ai.git
+```
+
+3. **安装全局依赖**
+
+```bash
+npm i -g pnpm@10.6.5 @microsoft/rush@5.150.0
+```
+
+4. **安装项目依赖**
+
+```bash
+rush install
+```
+
+5. **构建项目**
+
+```bash
+rush build
+```
+
+6. **运行文档或示例**
+
+```bash
+rush dev:docs                  # 在 apps/docs 启动文档站(含增量构建)
+rush dev:demo-fixed-layout     # 运行固定布局示例
+rush dev:demo-free-layout      # 运行自由布局示例
+```
+
+## 常用命令(Rush 自定义)
+
+```bash
+rush build            # 构建所有包
+rush build:watch      # 增量构建并监听
+rush lint             # 运行 ESLint 检查
+rush lint:fix         # 自动修复 ESLint 问题
+rush ts-check         # TypeScript 类型检查
+rush test             # 运行各包的测试脚本(按包聚合)
+rush e2e:test         # 运行所有 e2e 测试
+rush e2e:update-screenshot # 更新 e2e 快照
+rush dep-check        # 自动检查依赖健康度
+```
+
+## 分支与提交规范
+
+- 分支命名:
+  - `feat/描述`(新功能)
+  - `fix/描述`(问题修复)
+  - `docs/描述`(文档变更)
+  - `chore/描述`(维护/杂项)
+- 提交信息(Conventional Commits):
+  - 格式:`type(scope): subject`,例如:
+
+```text
+feat(editor): 支持节点批量对齐
+```
+
+  - 常用类型:`feat`、`fix`、`docs`、`style`、`refactor`、`test`、`chore`
+  - 仓库已启用 commitlint 校验(commit-msg 钩子),提交信息将被自动检查;同时 pre-commit 会运行 lint-staged(自动更新许可证头、eslint 修复)与 `rush check` 校验。
+
+## 开发与质量保障
+
+- 本地开发建议:
+  - 先执行 `rush build:watch`,再在对应 demo 或 docs 目录运行开发命令(如 `rush dev:docs`)。
+  - 修改代码后,确保通过:`rush lint`、`rush ts-check`、`rush build`、`rush test`。
+- 测试说明:
+  - e2e 用例位于 `e2e/` 目录,可通过 `rush e2e:test` 运行,或更新快照 `rush e2e:update-screenshot`。
+
+## Pull Request 流程
+
+1. 从 `main` 创建你的工作分支(遵循分支命名规范)。
+2. 编码并补充测试/文档。
+3. 本地通过质量校验(lint、ts-check、build、test)。
+4. 提交 PR:填写说明、关联 Issue,并注意使用模板。
+5. 评审与 CI:维护者会进行代码评审,CI 全绿后即可合并。
+
+## 文档贡献
+
+- 文档位置:`apps/docs/src/zh/**`(中文)与 `apps/docs/src/en/**`(英文)。
+- 本地预览:执行 `rush dev:docs` 启动 Rspress 文档站。
+- 若需自动生成 API 文档,可在 `apps/docs` 目录执行 `rushx docs`(调用脚本生成)。
+
+## 常见问题
+
+- pnpm-lock 合并冲突:仓库已在 `post-checkout` 钩子中配置合并策略,通常可避免锁文件冲突。
+- Node 版本:请确保使用 Node 18+,否则可能出现依赖或构建失败。
+
+## 报告问题
+
+- 请在 GitHub 提交 Issue:https://github.com/bytedance/flowgram.ai/issues/new/choose
+  - 描述问题、复现步骤、期望与实际行为,必要时附代码示例。
+
+## 许可证
+
+- 本项目遵循 MIT 许可证。提交代码即默认同意相关条款。

+ 69 - 1
apps/docs/src/zh/guide/free-layout/node.mdx

@@ -52,7 +52,75 @@ const nodeData: FlowNodeJSON = {
 
 ## 节点定义
 
-在自由布局场景,节点定义用于声明节点的初始化位置/大小,端口,表单渲染等, 详细见 [声明节点](/guide/getting-started/create-free-layout-simple.html#4-声明节点)
+在自由布局场景,节点定义用于声明节点的初始化位置/大小,端口,表单渲染等。
+
+
+```tsx pure title="node-registries.tsx"
+import { WorkflowNodeRegistry, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
+
+/**
+ * You can customize your own node registry
+ * 你可以自定义节点的注册器
+ */
+export const nodeRegistries: WorkflowNodeRegistry[] = [
+  {
+    type: 'start',
+    meta: {
+      isStart: true, // 标记为开始节点
+      deleteDisable: true, // 开始节点不能删除
+      copyDisable: true, // 开始节点不能复制
+      defaultPorts: [{ type: 'output' }], // 用于定义节点的输入和输出端口, 开始节点只有输出端口
+      // useDynamicPort: true, // 用于动态端口,会寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
+    },
+    /**
+     * 配置节点表单的校验及渲染,
+     * 注:validate 采用数据和渲染分离,保证节点即使不渲染也能对数据做校验
+     */
+    formMeta: {
+      validateTrigger: ValidateTrigger.onChange,
+      validate: {
+        title: ({ value }) => (value ? undefined : 'Title is required'),
+      },
+      /**
+       * Render form
+       */
+      render: () => (
+       <>
+          <Field name="title">
+            {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
+          </Field>
+          <Field name="content">
+            {({ field }) => <input onChange={field.onChange} value={field.value}/>}
+          </Field>
+        </>
+      )
+    },
+  },
+  {
+    type: 'end',
+    meta: {
+      deleteDisable: true,
+      copyDisable: true,
+      defaultPorts: [{ type: 'input' }],
+    },
+    formMeta: {
+      // ...
+    }
+  },
+  {
+    type: 'custom',
+    meta: {
+    },
+    formMeta: {
+      // ...
+    },
+    defaultPorts: [{ type: 'output' }, { type: 'input' }], // 普通节点有两个端口
+  },
+];
+
+```
+
+
 
 
 ## 当前渲染节点获取

+ 4 - 3
apps/docs/src/zh/guide/getting-started/_meta.json

@@ -1,5 +1,6 @@
 [
-  "install",
-  "create-free-layout-simple",
-  "create-fixed-layout-simple"
+  "introduction",
+  "quick-start",
+  "free-layout",
+  "fixed-layout"
 ]

+ 0 - 372
apps/docs/src/zh/guide/getting-started/create-fixed-layout-simple.mdx

@@ -1,372 +0,0 @@
-
-# 创建固定布局画布
-
-本案例可通过 `npx @flowgram.ai/create-app@latest fixed-layout-simple` 安装,完整代码及效果见:
-
-<div className="rs-tip">
-  <a className="rs-link" href="/examples/fixed-layout/fixed-layout-simple.html">
-    固定布局基础用法
-  </a>
-</div>
-
-文件结构:
-
-```
-- hooks
-  - use-editor-props.ts # 画布配置
-- components
-  - base-node.tsx # 节点渲染
-  - tools.tsx # 画布工具栏
-- initial-data.ts # 初始化数据
-- node-registries.ts # 节点配置
-- app.tsx # 画布入口
-```
-
-### 1. 画布入口
-
-- `FixedLayoutEditorProvider`: 画布配置器, 内部会生成 react-context 供子组件消费
-- `EditorRenderer`: 为最终渲染的画布,可以包装在其他组件下边方便定制画布位置
-
-```tsx pure title="app.tsx"
-
-import {
-  FixedLayoutEditorProvider,
-  EditorRenderer,
-} from '@flowgram.ai/fixed-layout-editor';
-
-import '@flowgram.ai/fixed-layout-editor/index.css'; // 加载样式
-
-import { useEditorProps } from './hooks/use-editor-props' // 画布详细的 props 配置
-import { Tools } from './components/tools' // 画布工具
-
-function App() {
-  const editorProps = useEditorProps()
-  return (
-    <FixedLayoutEditorProvider {...editorProps}>
-      <EditorRenderer className="demo-editor" />
-      <Tools />
-    </FixedLayoutEditorProvider>
-  );
-}
-```
-
-### 2. 配置画布
-
-画布配置采用声明式,提供 数据、渲染、事件、插件相关配置
-
-```tsx pure title="hooks/use-editor-props.tsx"
-import { useMemo } from 'react';
-import { type FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor';
-import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
-import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
-
-import { initialData } from './initial-data' // 初始化数据
-import { nodeRegistries } from './node-registries' // 节点声明配置
-import { BaseNode } from './base-node' // 节点渲染
-
-export function useEditorProps(
-): FixedLayoutProps {
-  return useMemo<FixedLayoutProps>(
-    () => ({
-      /**
-       * 初始化数据
-       */
-      initialData,
-      /**
-       * 画布节点定义
-       */
-      nodeRegistries,
-      /**
-       * 可以通过 key 自定义 UI 组件, 比如添加按钮,这里提供了一套 semi 组件方便快速验证, 如果需要深度定制,参考:
-       * https://github.com/bytedance/flowgram.ai/blob/main/packages/materials/fixed-semi-materials/src/components/index.tsx
-       */
-      materials: {
-        components: {
-          ...defaultFixedSemiMaterials,
-          // [FlowRendererKey.ADDER]: NodeAdder,
-          // [FlowRendererKey.BRANCH_ADDER]: BranchAdder,
-        },
-        renderDefaultNode: BaseNode, // 节点渲染组件
-      },
-      /**
-       * 节点引擎, 用于渲染节点表单
-       */
-      nodeEngine: {
-        enable: true,
-      },
-      /**
-       * 画布历史记录, 用于控制 redo/undo
-       */
-      history: {
-        enable: true,
-        enableChangeNode: true, // 用于监听节点表单数据变化
-      },
-      /**
-       * 画布初始化回调
-       */
-      onInit: ctx => {
-        // 如果要动态加载数据,可以通过如下方法异步执行
-        // ctx.docuemnt.fromJSON(initialData)
-      },
-      /**
-       * 画布第一次渲染完成回调
-       */
-      onAllLayersRendered: (ctx) => {},
-      /**
-       * 画布销毁回调
-       */
-      onDispose: () => { },
-      plugins: () => [
-        /**
-         * 缩略图插件
-         */
-        createMinimapPlugin({}),
-      ],
-    }),
-    [],
-  );
-}
-
-```
-
-
-### 3. 配置数据
-
-画布文档数据采用树形结构,支持嵌套
-
-:::note 文档数据基本结构:
-
-- nodes `array` 节点列表, 支持嵌套
-
-:::
-
-:::note 节点数据基本结构:
-
-
-- id: `string` 节点唯一标识, 必须保证唯一
-- meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
-- type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
-- data: `object` 节点表单数据
-- blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming`
-
-:::
-
-```tsx pure title="initial-data.tsx"
-import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
-
-/**
- * 配置流程数据,数据为 blocks 嵌套的格式
- */
-export const initialData: FlowDocumentJSON = {
-  nodes: [
-    // 开始节点
-    {
-      id: 'start_0',
-      type: 'start',
-      data: {
-        title: 'Start',
-        content: 'start content'
-      },
-      blocks: [],
-    },
-    // 分支节点
-    {
-      id: 'condition_0',
-      type: 'condition',
-      data: {
-        title: 'Condition'
-      },
-      blocks: [
-        {
-          id: 'branch_0',
-          type: 'block',
-          data: {
-            title: 'Branch 0',
-            content: 'branch 1 content'
-          },
-          blocks: [
-            {
-              id: 'custom_0',
-              type: 'custom',
-              data: {
-                title: 'Custom',
-                content: 'custrom content'
-              },
-            },
-          ],
-        },
-        {
-          id: 'branch_1',
-          type: 'block',
-          data: {
-            title: 'Branch 1',
-            content: 'branch 1 content'
-          },
-          blocks: [],
-        },
-      ],
-    },
-    // 结束节点
-    {
-      id: 'end_0',
-      type: 'end',
-      data: {
-        title: 'End',
-        content: 'end content'
-      },
-    },
-  ],
-};
-
-```
-
-### 4. 声明节点
-
-声明节点可以用于确定节点的类型及渲染方式
-
-```tsx pure title="node-registries.tsx"
-import { FlowNodeRegistry, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
-
-/**
- * 自定义节点注册
- */
-export const nodeRegistries: FlowNodeRegistry[] = [
-  {
-    /**
-     * 自定义节点类型
-     */
-    type: 'condition',
-    /**
-     * 自定义节点扩展:
-     *  - loop: 扩展为循环节点
-     *  - start: 扩展为开始节点
-     *  - dynamicSplit: 扩展为分支节点
-     *  - end: 扩展为结束节点
-     *  - tryCatch: 扩展为 tryCatch 节点
-     *  - default: 扩展为普通节点 (默认)
-     */
-    extend: 'dynamicSplit',
-    /**
-     * 节点配置信息
-     */
-    meta: {
-      // isStart: false, // 是否为开始节点
-      // isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点
-      // draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽
-      // selectable: false, // 触发器等开始节点不能被框选
-      // deleteDisable: true, // 禁止删除
-      // copyDisable: true, // 禁止copy
-      // addDisable: true, // 禁止添加
-    },
-    /**
-     * 配置节点表单的校验及渲染,
-     * 注:validate 采用数据和渲染分离,保证节点即使不渲染也能对数据做校验
-     */
-    formMeta: {
-      validateTrigger: ValidateTrigger.onChange,
-      validate: {
-        title: ({ value }) => (value ? undefined : 'Title is required'),
-      },
-      /**
-       * Render form
-       */
-      render: () => (
-       <>
-          <Field name="title">
-            {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
-          </Field>
-          <Field name="content">
-            {({ field }) => <input onChange={field.onChange} value={field.value}/>}
-          </Field>
-        </>
-      )
-    },
-  },
-];
-
-```
-### 5. 渲染节点
-
-渲染节点用于添加样式、事件及表单渲染的位置
-
-```tsx pure title="components/base-node.tsx"
-import { useNodeRender } from '@flowgram.ai/fixed-layout-editor';
-
-export const BaseNode = () => {
-  /**
-   * 提供节点渲染相关的方法
-   */
-  const nodeRender = useNodeRender();
-  /**
-   * 只有在节点引擎开启时候才能使用表单
-   */
-  const form = nodeRender.form;
-
-  return (
-    <div
-      className="demo-fixed-node"
-      onMouseEnter={nodeRender.onMouseEnter}
-      onMouseLeave={nodeRender.onMouseLeave}
-      onMouseDown={e => {
-        // 触发拖拽
-        nodeRender.startDrag(e);
-        e.stopPropagation();
-      }}
-      style={{
-        // BlockOrderIcon 表示为分支的第一个节点,BlockIcon 则表示整个 condition 的头部节点
-        ...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
-        outline: form?.state.invalid ? '1px solid red' : 'none', // 表单校验错误让边框标红
-      }}
-    >
-      {
-        // 表单渲染通过 formMeta 生成
-        form?.render()
-      }
-    </div>
-  );
-};
-
-```
-
-
-### 6. 添加工具
-
-工具主要用于控制画布缩放等操作, 工具汇总在 `usePlaygroundTools` 中, 而 `useClientContext` 用于获取画布的上下文, 里边包含画布的核心模块如 `history`
-
-```tsx pure title="components/tools.tsx"
-import { useEffect, useState } from 'react'
-import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
-
-export function Tools() {
-  const { history } = useClientContext();
-  const tools = usePlaygroundTools();
-  const [canUndo, setCanUndo] = useState(false);
-  const [canRedo, setCanRedo] = useState(false);
-
-  useEffect(() => {
-    const disposable = history.undoRedoService.onChange(() => {
-      setCanUndo(history.canUndo());
-      setCanRedo(history.canRedo());
-    });
-    return () => disposable.dispose();
-  }, [history]);
-
-  return <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 16, display: 'flex', gap: 8 }}>
-    <button onClick={() => tools.zoomin()}>ZoomIn</button>
-    <button onClick={() => tools.zoomout()}>ZoomOut</button>
-    <button onClick={() => tools.fitView()}>Fitview</button>
-    <button onClick={() => tools.changeLayout()}>ChangeLayout</button>
-    <button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
-    <button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
-    <span>{Math.floor(tools.zoom * 100)}%</span>
-  </div>
-}
-
-```
-### 7. 效果
-
-import { FixedLayoutSimple } from '../../../../components';
-
-<div style={{ position: 'relative', width: '100%', height: '600px'}}>
-  <FixedLayoutSimple />
-</div>

+ 0 - 349
apps/docs/src/zh/guide/getting-started/create-free-layout-simple.mdx

@@ -1,349 +0,0 @@
-
-# 创建自由布局画布
-
-本案例可通过 `npx @flowgram.ai/create-app@latest free-layout-simple` 安装,完整代码及效果见:
-
-<div className="rs-tip">
-  <a className="rs-link" href="/examples/free-layout/free-layout-simple.html">
-    自由布局基础用法
-  </a>
-</div>
-
-
-文件结构:
-
-```
-- hooks
-  - use-editor-props.ts # 画布配置
-- components
-  - base-node.tsx # 节点渲染
-  - tools.tsx # 画布工具栏
-- initial-data.ts # 初始化数据
-- node-registries.ts # 节点配置
-- app.tsx # 画布入口
-```
-
-### 1. 画布入口
-
-- `FreeLayoutEditorProvider`: 画布配置器, 内部会生成 react-context 供子组件消费
-- `EditorRenderer`: 为最终渲染的画布,可以包装在其他组件下边方便定制画布位置
-
-```tsx pure title="app.tsx"
-
-import {
-  FreeLayoutEditorProvider,
-  EditorRenderer,
-} from '@flowgram.ai/free-layout-editor';
-
-import '@flowgram.ai/free-layout-editor/index.css'; // 加载样式
-
-import { useEditorProps } from './use-editor-props' // 画布详细的 props 配置
-import { Tools } from './components/tools' // 画布工具
-
-function App() {
-  const editorProps = useEditorProps()
-  return (
-    <FreeLayoutEditorProvider {...editorProps}>
-      <EditorRenderer className="demo-editor" />
-      <Tools />
-    </FreeLayoutEditorProvider>
-  );
-}
-```
-
-### 2. 配置画布
-
-画布配置采用声明式,提供 数据、渲染、事件、插件相关配置
-
-```tsx pure title="hooks/use-editor-props.tsx"
-import { useMemo } from 'react';
-import { type FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
-import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
-
-import { initialData } from './initial-data' // 初始化数据
-import { nodeRegistries } from './node-registries' // 节点声明配置
-import { BaseNode } from './components/base-node' // 节点渲染
-
-export function useEditorProps(
-): FreeLayoutProps {
-  return useMemo<FreeLayoutProps>(
-    () => ({
-      /**
-       * 初始化数据
-       */
-      initialData,
-      /**
-       * 画布节点定义
-       */
-      nodeRegistries,
-      /**
-       * 物料
-       */
-      materials: {
-        renderDefaultNode: BaseNode, // 节点渲染组件
-      },
-      /**
-       * 节点引擎, 用于渲染节点表单
-       */
-      nodeEngine: {
-        enable: true,
-      },
-      /**
-       * 画布历史记录, 用于控制 redo/undo
-       */
-      history: {
-        enable: true,
-        enableChangeNode: true, // 用于监听节点表单数据变化
-      },
-      /**
-       * 画布初始化回调
-       */
-      onInit: ctx => {
-        // 如果要动态加载数据,可以通过如下方法异步执行
-        // ctx.docuemnt.fromJSON(initialData)
-      },
-      /**
-       * 画布第一次渲染完整回调
-       */
-      onAllLayersRendered: (ctx) => {},
-      /**
-       * 画布销毁回调
-       */
-      onDispose: () => { },
-      plugins: () => [
-        /**
-         * 缩略图插件
-         */
-        createMinimapPlugin({}),
-      ],
-    }),
-    [],
-  );
-}
-
-```
-
-
-### 3. 配置数据
-
-画布文档数据采用树形结构,支持嵌套
-
-:::note 文档数据基本结构:
-
-- nodes `array` 节点列表, 支持嵌套
-- edges `array` 边列表
-
-:::
-
-:::note 节点数据基本结构:
-
-- id: `string` 节点唯一标识, 必须保证唯一
-- meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
-- type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
-- data: `object` 节点表单数据, 业务可自定义
-- blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming`, 目前会存子画布的节点
-- edges: `array` 子画布的边数据
-
-:::
-
-:::note 边数据基本结构:
-
-- sourceNodeID: `string` 开始节点 id
-- targetNodeID: `string` 目标节点 id
-- sourcePortID?: `string | number` 开始端口 id, 缺省则采用开始节点的默认端口
-- targetPortID?: `string | number` 目标端口 id, 缺省则采用目标节点的默认端口
-
-:::
-
-
-```tsx pure title="initial-data.ts"
-import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
-
-export const initialData: WorkflowJSON = {
-  nodes: [
-    {
-      id: 'start_0',
-      type: 'start',
-      meta: {
-        position: { x: 0, y: 0 },
-      },
-      data: {
-        title: 'Start',
-        content: 'Start content'
-      },
-    },
-    {
-      id: 'node_0',
-      type: 'custom',
-      meta: {
-        position: { x: 400, y: 0 },
-      },
-      data: {
-        title: 'Custom',
-        content: 'Custom node content'
-      },
-    },
-    {
-      id: 'end_0',
-      type: 'end',
-      meta: {
-        position: { x: 800, y: 0 },
-      },
-      data: {
-        title: 'End',
-        content: 'End content'
-      },
-    },
-  ],
-  edges: [
-    {
-      sourceNodeID: 'start_0',
-      targetNodeID: 'node_0',
-    },
-    {
-      sourceNodeID: 'node_0',
-      targetNodeID: 'end_0',
-    },
-  ],
-};
-
-
-```
-
-### 4. 声明节点
-
-声明节点可以用于确定节点的类型及渲染方式
-
-```tsx pure title="node-registries.tsx"
-import { WorkflowNodeRegistry, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
-
-/**
- * You can customize your own node registry
- * 你可以自定义节点的注册器
- */
-export const nodeRegistries: WorkflowNodeRegistry[] = [
-  {
-    type: 'start',
-    meta: {
-      isStart: true, // 标记为开始节点
-      deleteDisable: true, // 开始节点不能删除
-      copyDisable: true, // 开始节点不能复制
-      defaultPorts: [{ type: 'output' }], // 用于定义节点的输入和输出端口, 开始节点只有输出端口
-      // useDynamicPort: true, // 用于动态端口,会寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
-    },
-    /**
-     * 配置节点表单的校验及渲染,
-     * 注:validate 采用数据和渲染分离,保证节点即使不渲染也能对数据做校验
-     */
-    formMeta: {
-      validateTrigger: ValidateTrigger.onChange,
-      validate: {
-        title: ({ value }) => (value ? undefined : 'Title is required'),
-      },
-      /**
-       * Render form
-       */
-      render: () => (
-       <>
-          <Field name="title">
-            {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
-          </Field>
-          <Field name="content">
-            {({ field }) => <input onChange={field.onChange} value={field.value}/>}
-          </Field>
-        </>
-      )
-    },
-  },
-  {
-    type: 'end',
-    meta: {
-      deleteDisable: true,
-      copyDisable: true,
-      defaultPorts: [{ type: 'input' }],
-    },
-    formMeta: {
-      // ...
-    }
-  },
-  {
-    type: 'custom',
-    meta: {
-    },
-    formMeta: {
-      // ...
-    },
-    defaultPorts: [{ type: 'output' }, { type: 'input' }], // 普通节点有两个端口
-  },
-];
-
-```
-### 5. 渲染节点
-
-渲染节点用于添加样式、事件及表单渲染的位置
-
-```tsx pure title="components/base-node.tsx"
-import { useNodeRender, WorkflowNodeRenderer } from '@flowgram.ai/free-layout-editor';
-
-export const BaseNode = () => {
-  /**
-   * 提供节点渲染相关的方法
-   */
-  const { form } = useNodeRender()
-  /**
-   * WorkflowNodeRenderer 会添加节点拖拽事件及 端口渲染,如果要深度定制,可以看该组件源代码:
-   * https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/components/workflow-node-renderer.tsx
-   */
-  return (
-    <WorkflowNodeRenderer className="demo-free-node" node={props.node}>
-      {
-        // 表单渲染通过 formMeta 生成
-        form?.render()
-      }
-    </WorkflowNodeRenderer>
-  )
-};
-
-```
-
-
-### 6. 添加工具
-
-工具主要用于控制画布缩放等操作, 工具汇总在 `usePlaygroundTools` 中, 而 `useClientContext` 用于获取画布的上下文, 里边包含画布的核心模块如 `history`
-
-```tsx pure title="components/tools.tsx"
-import { useEffect, useState } from 'react'
-import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
-
-export function Tools() {
-  const { history } = useClientContext();
-  const tools = usePlaygroundTools();
-  const [canUndo, setCanUndo] = useState(false);
-  const [canRedo, setCanRedo] = useState(false);
-
-  useEffect(() => {
-    const disposable = history.undoRedoService.onChange(() => {
-      setCanUndo(history.canUndo());
-      setCanRedo(history.canRedo());
-    });
-    return () => disposable.dispose();
-  }, [history]);
-
-  return <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}>
-    <button onClick={() => tools.zoomin()}>ZoomIn</button>
-    <button onClick={() => tools.zoomout()}>ZoomOut</button>
-    <button onClick={() => tools.fitView()}>Fitview</button>
-    <button onClick={() => tools.autoLayout()}>AutoLayout</button>
-    <button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
-    <button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
-    <span>{Math.floor(tools.zoom * 100)}%</span>
-  </div>
-}
-```
-### 7. 效果
-
-import { FreeLayoutSimple } from '../../../../components';
-
-<div style={{ position: 'relative', width: '100%', height: '600px'}}>
-  <FreeLayoutSimple />
-</div>

+ 378 - 0
apps/docs/src/zh/guide/getting-started/fixed-layout.mdx

@@ -0,0 +1,378 @@
+# 固定布局
+
+import {
+  PackageManagerTabs
+  // @ts-ignore
+} from '@theme';
+import { FixedLayoutCodePreview } from '@components/code-preview';
+import step1 from '@components/fixed-examples/step-1.tsx?raw';
+import step2 from '@components/fixed-examples/step-2.tsx?raw';
+import step3 from '@components/fixed-examples/step-3.tsx?raw';
+import step4 from '@components/fixed-examples/step-4.tsx?raw';
+import step5App from '@components/fixed-examples/step-5/app.tsx?raw';
+import step5UseEditorProps from '@components/fixed-examples/step-5/use-editor-props.tsx?raw';
+import step5InitialData from '@components/fixed-examples/step-5/initial-data.ts?raw';
+import step5NodeRegistries from '@components/fixed-examples/step-5/node-registries.tsx?raw';
+import step5NodeRender from '@components/fixed-examples/step-5/node-render.tsx?raw';
+import step5Adder from '@components/fixed-examples/step-5/adder.tsx?raw';
+import step6App from '@components/fixed-examples/step-6/app.tsx?raw';
+import step6UseEditorProps from '@components/fixed-examples/step-6/use-editor-props.tsx?raw';
+import step6InitialData from '@components/fixed-examples/step-6/initial-data.ts?raw';
+import step6NodeRegistries from '@components/fixed-examples/step-6/node-registries.tsx?raw';
+import step6NodeRender from '@components/fixed-examples/step-6/node-render.tsx?raw';
+import step6Adder from '@components/fixed-examples/step-6/adder.tsx?raw';
+import step7App from '@components/fixed-examples/step-7/app.tsx?raw';
+import step7UseEditorProps from '@components/fixed-examples/step-7/use-editor-props.tsx?raw';
+import step7InitialData from '@components/fixed-examples/step-7/initial-data.ts?raw';
+import step7NodeRegistries from '@components/fixed-examples/step-7/node-registries.tsx?raw';
+import step7NodeRender from '@components/fixed-examples/step-7/node-render.tsx?raw';
+import step7Adder from '@components/fixed-examples/step-7/adder.tsx?raw';
+import step7Tools from '@components/fixed-examples/step-7/tools.tsx?raw';
+import step7Minimap from '@components/fixed-examples/step-7/minimap.tsx?raw';
+
+
+
+## Step.0 - 安装依赖
+
+1. 安装编辑器包
+
+<PackageManagerTabs command={{
+  "npm": "npm install @flowgram.ai/fixed-layout-editor @flowgram.ai/fixed-semi-materials",
+  "pnpm": "pnpm add @flowgram.ai/fixed-layout-editor @flowgram.ai/fixed-semi-materials",
+  "yarn": "yarn add @flowgram.ai/fixed-layout-editor @flowgram.ai/fixed-semi-materials",
+  "bun": "bun add @flowgram.ai/fixed-layout-editor @flowgram.ai/fixed-semi-materials",
+}} />
+
+2. 安装 styled-components(若尚未安装)
+
+<PackageManagerTabs command={{
+  "npm": "npm install styled-components",
+  "pnpm": "pnpm add styled-components",
+  "yarn": "yarn add styled-components",
+  "bun": "bun add styled-components",
+}} />
+
+## Step.1 - 引入画布组件
+
+1. 引入样式文件,确保基础样式生效:
+   ```tsx
+   import '@flowgram.ai/fixed-layout-editor/index.css';
+   ```
+
+2. 使用 `FixedLayoutEditorProvider` 提供编辑器上下文,`EditorRenderer` 负责渲染画布;并通过 `materials.components` 引入默认的固定布局 Semi 组件集:
+   ```tsx
+   const FlowGramApp = () => (
+     <FixedLayoutEditorProvider
+       materials={{ components: defaultFixedSemiMaterials }}
+     >
+       <EditorRenderer />
+     </FixedLayoutEditorProvider>
+   );
+   ```
+
+3. 其余文件保持默认导出即可。
+
+> 预期效果:页面加载后仅展示一个空白画布,无任何节点或连线。
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step1
+}} />
+
+
+## Step.2 - 实现节点组件
+
+1. 导入节点渲染相关 API:
+   - `useNodeRender`:获取节点上下文(如拖拽、悬停、高亮、表单等)。
+   - `FlowNodeEntity`:节点实体类型,用于声明 `NodeRender` 的 props。
+
+2. 创建 `NodeRender` 组件,自定义节点尺寸与样式,并接入拖拽与表单渲染:
+   ```tsx
+   import { useNodeRender, FlowNodeEntity } from '@flowgram.ai/fixed-layout-editor';
+
+   export const NodeRender = ({ node }: { node: FlowNodeEntity }) => {
+     const { onMouseEnter, onMouseLeave, startDrag, form, dragging, activated } = useNodeRender();
+     return (
+       <div
+         onMouseEnter={onMouseEnter}
+         onMouseLeave={onMouseLeave}
+         onMouseDown={(e) => { startDrag(e); e.stopPropagation(); }}
+         style={{ width: 280, minHeight: 88, background: '#fff', borderRadius: 8, opacity: dragging ? 0.3 : 1, /* ... */ }}
+       >
+         {form?.render()}
+       </div>
+     );
+   };
+   ```
+
+3. 在 `FixedLayoutEditorProvider` 中注册:
+   - `materials.renderDefaultNode` 指定默认节点渲染器为 `NodeRender`。
+   - `nodeRegistries` 声明可用节点类型(示例为 `custom`)。
+   - `initialData` 提供一个初始节点,类型为 `custom`。
+
+> 预期效果:画布中出现一个可拖拽的自定义样式节点。
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step2
+}} />
+
+
+## Step.3 - 自定义添加节点组件与删除节点按钮
+
+1. 为节点添加删除按钮
+   - 在 `NodeRender` 中通过 `useClientContext()` 获取 `ctx`,点击按钮调用 `ctx.operation.deleteNode(node)` 删除当前节点。
+   - 注意阻止事件冒泡:`e.stopPropagation()`,避免干扰画布的选择/拖拽行为。
+   ```tsx
+   const ctx = useClientContext();
+   <button onClick={(e) => { e.stopPropagation(); ctx.operation.deleteNode(node); }}>×</button>
+   ```
+
+2. 自定义添加节点组件 `Adder`
+   - 使用 `useService(FlowOperationService)` 与 `usePlayground()` 封装 `handleAdd` 方法:在指定节点之后插入一个新节点,并滚动到视图中心。
+   - 基于 `hoverActivated` 切换 UI:悬停时显示加号与更大的点击区域;只读模式下不显示。
+   ```tsx
+   const { handleAdd } = useAddNode();
+   const Adder = ({ from, hoverActivated }) => (
+     <div onClick={() => handleAdd({ type: 'custom', id: `custom_${Date.now()}` }, from)}>
+       {hoverActivated ? <span>+</span> : null}
+     </div>
+   );
+   ```
+
+3. 在 `materials.components` 中注册 `Adder`
+   - 通过 `FlowRendererKey.ADDER` 覆盖默认的“添加节点”渲染器。
+   ```tsx
+   materials={{
+     renderDefaultNode: NodeRender,
+     components: { ...defaultFixedSemiMaterials, [FlowRendererKey.ADDER]: Adder },
+   }}
+   ```
+
+4. 初始化数据与视图适配
+   - `initialData` 提供基础流程:`start -> custom -> end`。
+   - 在 `onAllLayersRendered` 中调用 `fitView`,让画布自动适配内容:
+   ```tsx
+   onAllLayersRendered={(ctx) => {
+     setTimeout(() => {
+       ctx.playground.config.fitView(ctx.document.root.bounds.pad(30));
+     }, 10);
+   }}
+   ```
+
+> 预期效果:
+>
+> • 画布展示一个由 `start`、`custom`、`end` 组成的基础流程,视图自动居中/缩放到合适范围。
+>
+> • 鼠标悬停到可添加位置时出现圆形加号按钮,点击即可在当前节点之后新增一个 `custom` 节点,并自动滚动到新节点。
+>
+> • 每个节点右上角显示“×”删除按钮,点击可删除该节点(只读模式下隐藏添加组件)。
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step3
+}} />
+
+## Step.4 - 引入插件
+
+:::info
+
+- `@flowgram.ai/minimap-plugin`:迷你地图插件,提供画布的小地图视图。
+
+:::
+
+1. 安装插件依赖
+
+<PackageManagerTabs command={{
+  "npm": "npm install @flowgram.ai/minimap-plugin",
+  "pnpm": "pnpm add @flowgram.ai/minimap-plugin",
+  "yarn": "yarn add @flowgram.ai/minimap-plugin",
+  "bun": "bun add @flowgram.ai/minimap-plugin",
+}} />
+
+2. 从对应包导入插件创建函数:
+  - `createMinimapPlugin` 用于生成画布缩略图。
+
+3. 在 `FixedLayoutEditorProvider` 的 `plugins` 属性中注册插件:
+  ```tsx
+  plugins={() => [
+    createMinimapPlugin({
+      enableDisplayAllNodes: true,
+    })
+  ]}
+  ```
+
+> 预期效果:
+>
+> • 画布右上角出现可拖拽/缩放的迷你地图,点击或拖拽缩略图可快速定位主画布。
+>
+> • 启用 `enableDisplayAllNodes: true` 后,迷你地图会显示所有节点,方便在流程较长时快速导航。
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step4
+}} />
+
+
+## Step.5 - 拆分文件
+
+为避免单个文件代码行数过长,我们需要将原本集中在一个组件中的编辑器配置、节点渲染、初始化数据等拆分为独立文件,便于维护、复用与协作。
+
+```sh
+- use-editor-props.tsx # 画布配置(集中管理 Provider 的 props)
+- node-render.tsx      # 节点渲染(含删除按钮)
+- initial-data.ts      # 初始化数据(start/custom/end)
+- node-registries.tsx  # 节点注册(示例仅注册 'custom')
+- adder.tsx            # 自定义添加节点组件(点击新增 custom 节点)
+- App.tsx              # 画布入口(挂载 EditorRenderer)
+```
+
+文件职责说明
+
+- `use-editor-props.tsx`:集中管理 FixedLayoutEditorProvider 的所有 props(插件、视图适配、材料、节点注册与初始数据):
+  - `plugins`:注册迷你地图插件 `createMinimapPlugin({ enableDisplayAllNodes: true })`。
+  - `onAllLayersRendered`:渲染完成后调用 `fitView`,让画布自动适配内容。
+  - `materials`:
+    - `renderDefaultNode` 指定默认节点渲染器为 `NodeRender`。
+    - `components` 合并 `defaultFixedSemiMaterials`,并用 `[FlowRendererKey.ADDER]: Adder` 覆盖默认添加器。
+  - `nodeRegistries` 与 `initialData` 分别来自独立文件。
+
+- `node-render.tsx`:定义自定义节点渲染器 `NodeRender`,设置节点外观并通过 `form?.render()` 渲染内部表单;同时在右上角提供“×”删除按钮(`useClientContext().operation.deleteNode(node)`)。
+
+- `initial-data.ts`:提供基础流程的初始数据,包含 `start -> custom -> end` 三个节点。
+
+- `node-registries.tsx`:声明节点类型集合(示例为仅注册 `'custom'`)。
+
+- `adder.tsx`:实现自定义添加节点组件 `Adder`,悬停时显示加号;点击时通过 `FlowOperationService.addFromNode` 在当前节点之后新增一个 `custom` 节点,并调用 `scrollToView` 自动定位新节点。
+
+- `App.tsx`:应用入口,从 `useEditorProps` 获取配置并挂载 `EditorRenderer`。
+
+> 预期效果:通过拆分文件,代码结构更清晰、职责更明确,后续扩展与团队协作更容易;界面效果与上一节一致(基础流程、删除按钮与添加节点组件可用,且含迷你地图与自动视图适配)。
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step5App,
+    '/use-editor-props.tsx': step5UseEditorProps,
+    '/initial-data.ts': step5InitialData,
+    '/node-registries.tsx': step5NodeRegistries,
+    '/node-render.tsx': step5NodeRender,
+    '/adder.tsx': step5Adder,
+}} />
+
+## Step.6 - 接入表单与历史记录
+
+1. 节点注册与扩展
+
+- `condition`:通过 `extend: 'dynamicSplit'` 将其扩展为“分支节点”,并在 `onAdd()` 中返回默认 `blocks`(多分支)。
+- `custom`:普通节点,在 `onAdd()` 中为其设置默认 `data.title` 与 `data.content`。
+- 可选的 `meta` 配置项可控制节点行为(是否可拖拽、选择、删除、复制、添加等)。
+
+2. 启用表单与历史
+
+在 `use-editor-props.tsx` 中:
+- `nodeEngine.enable = true`:开启节点引擎,允许为节点类型配置 `formMeta`。
+- `history.enable = true` 与 `history.enableChangeNode = true`:启用撤销/重做,并监听节点数据变化(例如表单字段变更)。
+- `history.onApply(ctx)`:在应用历史记录后触发,可用于自动保存(示例中打印 `ctx.document.toJSON()`)。
+- `getNodeDefaultRegistry(type)`:为未显式注册的类型提供默认配置:
+  - `meta.defaultExpanded = true`:默认展开节点的内部内容区域。
+  - `formMeta.render`:渲染表单。本示例通过 `<Field<string> name="title">` 与 `<Field<string> name="content">` 分别展示标题与一个可编辑输入框。
+  ```tsx
+  getNodeDefaultRegistry(type) {
+    return {
+      type,
+      meta: { defaultExpanded: true },
+      formMeta: {
+        render: () => (
+          <>
+            <Field<string> name="title">{({ field }) => <div>{field.value}</div>}</Field>
+            <Field<string> name="content">
+              <input />
+            </Field>
+          </>
+        ),
+      },
+    };
+  }
+  ```
+
+3. 初始化数据与渲染
+
+- 在 `initial-data.ts` 中,包含一个 `start` 节点、一个带三条分支的 `condition` 节点(其中一条分支包含 `custom`,另一条为 `break`,还有一条为空),以及一个 `end` 节点。
+- 各节点携带 `data.title` 与 `data.content`,`NodeRender` 中的 `form?.render()` 会将这些表单字段渲染到节点外壳内。
+- `Adder` 组件点击后新增 `custom` 节点,默认 `title: 'New Custom Node'` 与 `content: 'Custom Node Content'`。
+
+> 预期效果:
+>
+> • 画布包含 `start`、`condition`(三分支)与 `end`,各节点显示其 `title` 与 `content`;可通过 `Adder` 在流程中快速新增 `custom` 节点。
+>
+> • 撤销/重做快捷键可用,节点移动、添加、删除以及表单输入变更都会纳入历史记录;触发历史应用时将执行示例中的自动保存回调。
+>
+> • 分支节点的展开区域默认打开,便于查看与编辑内部内容。
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step6App,
+    '/use-editor-props.tsx': step6UseEditorProps,
+    '/initial-data.ts': step6InitialData,
+    '/node-registries.tsx': step6NodeRegistries,
+    '/node-render.tsx': step6NodeRender,
+    '/adder.tsx': step6Adder,
+}} />
+
+## Step.7 - 创建工具栏
+
+1. 引入工具栏与迷你地图组件
+
+- 在 `App.tsx` 中引入并渲染 `<Tools />` 与自定义 `<Minimap />`,与 `<EditorRenderer />` 同级放置于 `FixedLayoutEditorProvider` 内,使其能够访问编辑器上下文与画布操作方法。
+
+2. 通过工具方法操控画布
+
+- 使用 `usePlaygroundTools()` 获取画布操作方法:`zoomin/zoomout`、`fitView`、`changeLayout` 等。
+- 实时显示缩放比例:通过 `tools.zoom` 读取当前画布缩放并展示百分比。
+
+3. 接入撤销/重做状态
+
+- 使用 `useClientContext()` 获取 `history`,并监听 `history.undoRedoService.onChange` 更新工具栏中的 `Undo/Redo` 可用态。
+- 在 `use-editor-props.tsx` 中确保开启历史:`history.enable = true` 与 `history.enableChangeNode = true`,使撤销/重做对节点数据与布局变更生效。
+
+4. 自定义迷你地图(可选)
+
+- 使用 `MinimapRender` 并自定义容器样式,将缩略图固定在左下角,避免遮挡主要交互区域。
+- 在 `use-editor-props.tsx` 中为迷你地图插件传入:`disableLayer: true` 与 `canvasStyle`(`canvasWidth/canvasHeight/canvasPadding`),获得紧凑的缩略图尺寸与边距。
+
+5. 保持前述功能
+
+- `NodeRender` 继续通过 `form?.render()` 渲染表单字段,并提供删除按钮;`Adder` 支持一键新增 `custom` 节点;`node-registries.tsx` 中维持 `condition/custom` 类型注册;`initial-data.ts` 中包含 `start → condition(三分支) → end` 的示例流程。
+
+> 预期效果:
+>
+> • 画面左下角出现工具栏,支持 `ZoomIn/ZoomOut`、`FitView`、`ChangeLayout` 等常用操作,并实时显示缩放比例;`Undo/Redo` 按钮会根据历史状态联动更新。
+>
+> • 左下角同时展示自定义样式的迷你地图,可点击/拖拽缩略图快速定位主画布;结合工具栏形成完整的编辑工具区,操作更高效。
+
+<FixedLayoutCodePreview files={{
+    '/App.tsx': step7App,
+    '/use-editor-props.tsx': step7UseEditorProps,
+    '/initial-data.ts': step7InitialData,
+    '/node-registries.tsx': step7NodeRegistries,
+    '/node-render.tsx': step7NodeRender,
+    '/adder.tsx': step7Adder,
+    '/tools.tsx': step7Tools,
+    '/minimap.tsx': step7Minimap,
+}} />
+
+## Step.8 - 了解更多
+
+<div style={{
+  display: "grid",
+  gridTemplateColumns: "1fr 1fr",
+  gap: "2rem",
+  marginTop: "1rem",
+}}>
+  <div>
+  了解更多固定布局用法
+  - [加载与保存](/guide/fixed-layout/load)
+  - [节点](/guide/fixed-layout/node)
+  - [复合节点](/guide/fixed-layout/composite-nodes)
+  </div>
+  <div>
+  了解 FlowGram.AI 更多功能
+  - [表单](/guide/form/form)
+  - [变量](/guide/variable/basic)
+  - [物料](/materials/introduction)
+  </div>
+</div>

+ 300 - 0
apps/docs/src/zh/guide/getting-started/free-layout.mdx

@@ -0,0 +1,300 @@
+# 自由布局
+
+import {
+  PackageManagerTabs
+  // @ts-ignore
+} from '@theme';
+import { CodePreview } from '@components/code-preview';
+import step1 from '@components/free-examples/step-1.tsx?raw';
+import step2 from '@components/free-examples/step-2.tsx?raw';
+import step3 from '@components/free-examples/step-3.tsx?raw';
+import step4 from '@components/free-examples/step-4.tsx?raw';
+import step5App from '@components/free-examples/step-5/app.tsx?raw';
+import step5InitialData from '@components/free-examples/step-5/initial-data.ts?raw';
+import step5UseEditorProps from '@components/free-examples/step-5/use-editor-props.tsx?raw';
+import step5NodeRender from '@components/free-examples/step-5/node-render.tsx?raw';
+import step5NodeRegistries from '@components/free-examples/step-5/node-registries.tsx?raw';
+import step6App from '@components/free-examples/step-6/app.tsx?raw';
+import step6InitialData from '@components/free-examples/step-6/initial-data.ts?raw';
+import step6UseEditorProps from '@components/free-examples/step-6/use-editor-props.tsx?raw';
+import step6NodeRender from '@components/free-examples/step-6/node-render.tsx?raw';
+import step6NodeRegistries from '@components/free-examples/step-6/node-registries.tsx?raw';
+import step7App from '@components/free-examples/step-7/app.tsx?raw';
+import step7InitialData from '@components/free-examples/step-7/initial-data.ts?raw';
+import step7UseEditorProps from '@components/free-examples/step-7/use-editor-props.tsx?raw';
+import step7NodeRender from '@components/free-examples/step-7/node-render.tsx?raw';
+import step7NodeRegistries from '@components/free-examples/step-7/node-registries.tsx?raw';
+import step7Tools from '@components/free-examples/step-7/tools.tsx?raw';
+import step7AddNode from '@components/free-examples/step-7/add-node.tsx?raw';
+import step7Minimap from '@components/free-examples/step-7/minimap.tsx?raw';
+
+## Step.0 - 安装依赖
+
+1. 安装编辑器包
+
+<PackageManagerTabs command={{
+  "npm": "npm install @flowgram.ai/free-layout-editor",
+  "pnpm": "pnpm add @flowgram.ai/free-layout-editor",
+  "yarn": "yarn add @flowgram.ai/free-layout-editor",
+  "bun": "bun add @flowgram.ai/free-layout-editor",
+}} />
+
+2. 安装 styled-components(若尚未安装)
+
+<PackageManagerTabs command={{
+  "npm": "npm install styled-components",
+  "pnpm": "pnpm add styled-components",
+  "yarn": "yarn add styled-components",
+  "bun": "bun add styled-components",
+}} />
+
+## Step.1 - 引入画布组件
+
+1. 引入样式文件,确保基础样式生效:
+   ```tsx
+   import '@flowgram.ai/free-layout-editor/index.css';
+   ```
+
+2. 使用 `FreeLayoutEditorProvider` 提供编辑器上下文,`EditorRenderer` 负责渲染画布:
+   ```tsx
+   const FlowGramApp = () => (
+     <FreeLayoutEditorProvider>
+       <EditorRenderer />
+     </FreeLayoutEditorProvider>
+   );
+   ```
+
+3. 其余文件保持默认导出即可。
+
+> 预期效果:页面加载后仅展示一个空白画布,无任何节点或连线。
+
+<CodePreview files={{
+    '/App.tsx': step1
+}} />
+
+## Step.2 - 实现节点组件并注册
+
+1. 导入节点渲染相关 Hook 与组件:
+   - `useNodeRender`:获取节点上下文(如表单)。
+   - `WorkflowNodeProps` & `WorkflowNodeRenderer`:定义并渲染节点外壳。
+
+2. 创建 `NodeRender` 组件,自定义节点尺寸与样式:
+   ```tsx
+   const NodeRender = (props: WorkflowNodeProps) => {
+     const { form } = useNodeRender();
+     return (
+       <WorkflowNodeRenderer
+         style={{ width: 280, height: 88, background: '#fff', borderRadius: 8, ... }}
+         node={props.node}
+       >
+         {form?.render()}
+       </WorkflowNodeRenderer>
+     );
+   };
+   ```
+
+3. 在 `FreeLayoutEditorProvider` 中注册:
+   - `materials.renderDefaultNode` 指定默认节点渲染器。
+   - `nodeRegistries` 声明可用节点类型(示例为 `custom`)。
+   - `initialData` 提供一个初始节点,位置 `{ x: 250, y: 100 }`。
+
+> 预期效果:画布中出现一个可拖拽的自定义样式节点。
+
+<CodePreview files={{
+    '/App.tsx': step2
+}} />
+
+## Step.3 - 添加多节点与连线
+
+1. 新增 `onAllLayersRendered` 回调,在所有图层渲染完成后调用 `ctx.tools.fitView(false)`,让画布自动适配内容。
+
+2. 新增 `canDeleteNode` & `canDeleteLine` 回调,返回 `true` 允许删除节点与连线。
+
+3. 扩展 `initialData`:
+   - 再增加一个同类型节点,位置 `{ x: 400, y: 0 }`。
+   - 在 `edges` 数组中添加一条连线,连接节点 `1` 与节点 `2`。
+
+> 预期效果:
+>
+> • 画布展示两个相连节点,并自动居中/缩放到合适视图。
+>
+> • 选中任意节点或连线,键盘删除键可删除选中元素。
+
+<CodePreview files={{
+    '/App.tsx': step3
+}} />
+
+## Step.4 - 引入插件
+
+:::info
+
+- `@flowgram.ai/free-snap-plugin`:节点对齐插件,使节点在网格上对齐。
+- `@flowgram.ai/minimap-plugin`:迷你地图插件,提供画布的小地图视图。
+
+:::
+
+1. 安装插件依赖
+
+<PackageManagerTabs command={{
+  "npm": "npm install @flowgram.ai/free-snap-plugin @flowgram.ai/minimap-plugin",
+  "pnpm": "pnpm add @flowgram.ai/free-snap-plugin @flowgram.ai/minimap-plugin",
+  "yarn": "yarn add @flowgram.ai/free-snap-plugin @flowgram.ai/minimap-plugin",
+  "bun": "bun add @flowgram.ai/free-snap-plugin @flowgram.ai/minimap-plugin",
+}} />
+
+2. 从对应包导入插件创建函数:
+  - `createFreeSnapPlugin` 用于节点网格对齐。
+  - `createMinimapPlugin` 用于生成画布缩略图。
+
+3. 在 `FreeLayoutEditorProvider` 的 `plugins` 属性中注册插件:
+  ```tsx
+  plugins={() => [
+    createMinimapPlugin({}),
+    createFreeSnapPlugin({})
+  ]}
+  ```
+
+> 预期效果:
+>
+> • 画布右上角出现可拖拽/缩放的迷你地图,点击或拖拽缩略图可快速定位主画布。
+>
+> • 拖拽节点时,节点会自动吸附到附近节点,便于快速对齐。
+
+<CodePreview files={{
+    '/App.tsx': step4
+}} />
+
+
+## Step.5 - 拆分文件
+
+为避免单个文件代码行数过长,我们需要将原本集中在一个组件中的编辑器配置、节点渲染、初始化数据等拆分为独立文件,便于维护、复用与协作。
+
+```sh
+- use-editor-props.ts # 画布配置
+- node-render.tsx # 节点渲染
+- initial-data.ts # 初始化数据
+- node-registries.ts # 节点配置
+- App.tsx # 画布入口
+```
+
+文件职责说明
+
+- `use-editor-props.tsx`:集中管理 FreeLayoutEditorProvider 的所有 props(插件、视图适配、材料、节点注册与初始数据)。
+- `node-render.tsx`:定义自定义节点渲染器 NodeRender,负责外观与内部表单渲染。
+- `initial-data.ts`:提供初始节点与连线。当前示例包含 5 个 custom 节点及多条连接关系。
+- `node-registries.tsx`:声明节点类型集合(示例为仅注册 'custom')。
+- `App.tsx`:应用入口,从 useEditorProps 获取配置并挂载 EditorRenderer。
+
+> 预期效果: 通过拆分文件,代码结构更清晰、职责更明确,后续代码更易扩展
+
+<CodePreview files={{
+    '/App.tsx': step5App,
+    '/use-editor-props.tsx': step5UseEditorProps,
+    '/initial-data.ts': step5InitialData,
+    '/node-registries.tsx': step5NodeRegistries,
+    '/node-render.tsx': step5NodeRender,
+}} />
+
+## Step.6 - 接入表单与历史记录
+
+1. 节点注册与端口配置
+
+- `start`:起始节点,不可删除,默认只有输出端口。
+- `end`:结束节点,不可删除,默认只有输入端口。
+- `custom`:普通节点,默认同时拥有输入与输出端口。
+
+2. 启用表单与历史记录
+
+在 `useEditorProps.tsx` 中:
+- `nodeEngine.enable = true`:开启节点引擎,允许为节点类型配置 `formMeta`。
+- `history.enable = true` 与 `history.enableChangeNode = true`:启用撤销/重做,并监听节点数据变化(例如表单变更)。
+- `getNodeDefaultRegistry(type)`:为未显式注册的类型提供默认配置:
+  - `meta.defaultExpanded = true`:默认展开节点的内部内容区域。
+  - `formMeta.render`:渲染表单。本示例通过 `<Field<string> name="title">` 渲染标题字段。
+
+3. 初始化数据与渲染
+
+- 在 `initial-data.ts` 中,为每个节点设置 `data.title`(如 `Start Node`、`Custom Node A/B/C`、`End Node`)。
+- `NodeRender` 中的 `form?.render()` 会将表单内容渲染进节点外壳,展示各节点的标题。
+
+> 预期效果:
+>
+> • 画布包含 `start`、多个 `custom` 与 `end` 节点,连接关系与初始数据一致。
+>
+> • 每个节点显示其 `title`;选中节点时可扩展为展示更多表单字段与交互。
+>
+> • 撤销/重做快捷键可用,可通过删除、移动节点操作进行验证。
+
+<CodePreview activeFile="/use-editor-props.tsx" files={{
+    '/App.tsx': step6App,
+    '/use-editor-props.tsx': step6UseEditorProps,
+    '/initial-data.ts': step6InitialData,
+    '/node-registries.tsx': step6NodeRegistries,
+    '/node-render.tsx': step6NodeRender,
+}} />
+
+## Step.7 - 创建工具栏
+
+1. 引入工具栏组件
+
+- 在 `App.tsx` 中引入 `<Tools />`,与 `<EditorRenderer />` 同级放置于 `FreeLayoutEditorProvider` 内,使其能够访问编辑器上下文与工具方法。
+
+2. 通过工具方法操控画布
+
+- 使用 `usePlaygroundTools()` 获取画布操作方法:`zoomin/zoomout`、`fitView`、`autoLayout`、`switchLineType` 等。
+- 切换连线样式:通过 `switchLineType` 在 `LineType.BEZIER`(贝塞尔)与 `LineType.LINE_CHART`(折线)之间切换。
+- 实时显示缩放比例:读取 `tools.zoom`,展示当前画布缩放百分比。
+
+3. 接入撤销/重做状态
+
+- 使用 `useClientContext()` 获取 `history`,并监听 `history.undoRedoService.onChange` 更新 `canUndo/canRedo` 按钮状态。
+- 在 `use-editor-props.tsx` 中确保开启历史:`history.enable = true` 与 `history.enableChangeNode = true`,使撤销/重做对节点数据变化生效。
+
+5. 扩展组件(可选)
+
+- Minimap:通过自定义 `MinimapRender` 容器样式将缩略图固定在右下角,提升定位效率。
+- AddNode:提供快速新增节点按钮,通过 `WorkflowDocument.createWorkflowNodeByType` 在画布中心创建并选中节点。
+
+> 预期效果:
+>
+> • 页面右下角出现工具栏,支持 ZoomIn/ZoomOut、FitView、AutoLayout 等常用操作,实时显示缩放比例。
+>
+> • 可切换连线样式(贝塞尔/折线),撤销/重做按钮会随历史状态自动启用/禁用。
+>
+> • 搭配 Minimap 与 AddNode 组件,形成完整的编辑工具区,操作更高效。
+
+<CodePreview activeFile="/tools.tsx" files={{
+    '/App.tsx': step7App,
+    '/use-editor-props.tsx': step7UseEditorProps,
+    '/initial-data.ts': step7InitialData,
+    '/node-registries.tsx': step7NodeRegistries,
+    '/tools.tsx': step7Tools,
+    '/add-node.tsx': step7AddNode,
+    '/minimap.tsx': step7Minimap,
+    '/node-render.tsx': step7NodeRender,
+}} />
+
+## Step.8 - 了解更多
+
+<div style={{
+  display: "grid",
+  gridTemplateColumns: "1fr 1fr",
+  gap: "2rem",
+  marginTop: "1rem",
+}}>
+  <div>
+  了解更多自由布局用法:
+  - [加载与保存](/guide/free-layout/load)
+  - [节点](/guide/free-layout/node)
+  - [线条](/guide/free-layout/line)
+  - [端口](/guide/free-layout/port)
+  - [子画布](/guide/free-layout/sub-canvas)
+  </div>
+  <div>
+  了解 FlowGram.AI 更多功能:
+  - [表单](/guide/form/form)
+  - [变量](/guide/variable/basic)
+  - [物料](/materials/introduction)
+  - [运行时](/guide/runtime/introduction)
+  </div>
+</div>

+ 0 - 32
apps/docs/src/zh/guide/getting-started/install.mdx

@@ -1,32 +0,0 @@
-# 安装
-
-import { PackageManagerTabs } from '@theme';
-
-## 通过 npx 安装
-
-<PackageManagerTabs command={{
-  "MacOS / Linux / WSL / Git Bash": "npx @flowgram.ai/create-app@latest",
-  "Windows PowerShell": 'npx "@flowgram.ai/create-app@latest"',
-}} />
-
-```shell
-# 选择 demo
-- fixed-layout # 固定布局最佳实践
-- free-layout # 自由布局最佳实践
-- fixed-layout-simple # 固定布局基础用法
-- free-layout-simple # 自由布局基础用法
-
-```
-
-## 通过 npm 安装
-
-<PackageManagerTabs command={{
-  "MacOS / Linux / WSL / Git Bash": "npm install @flowgram.ai/fixed-layout-editor",
-  "Windows PowerShell": 'npm install "@flowgram.ai/fixed-layout-editor"',
-}} />
-
-<PackageManagerTabs command={{
-  "MacOS / Linux / WSL / Git Bash": "npm install @flowgram.ai/free-layout-editor",
-  "Windows PowerShell": 'npm install "@flowgram.ai/free-layout-editor"',
-}} />
-

+ 165 - 0
apps/docs/src/zh/guide/getting-started/introduction.mdx

@@ -0,0 +1,165 @@
+# 简介
+
+FlowGram 是一个工作流开发框架与工具集。帮助开发者以更快、更简单的方式搭建 AI 工作流平台。
+FlowGram 内置开箱开箱即用的工作流开发能力:可视化流程画布、节点配置表单、变量作用域链,以及开箱即用的物料。
+使用 FlowGram 构建你自己的 AI 工作流平台吧。
+
+## 为什么选择 FlowGram
+
+FlowGram 的诞生源于字节跳动内部构建多样化 AI 工作流平台的需求。
+这些平台通常具有复杂的业务逻辑和流程,从零开始构建不仅耗时,而且开发和维护成本极高。
+
+许多开发者最初尝试使用业界主流的图形可视化库来搭建工作流平台。
+然而,这些通用库无法解决工作流场景下的核心问题,开发者仍需自行处理节点数据管理、动态表单、数据校验、变量作用域链等一系列难题,这导致开发效率低下且后期维护困难。
+
+为了解决这些痛点,我们推出了 FlowGram,一个专为工作流场景设计的开发框架,旨在帮助开发者提升工作流平台的开发效率、缩短开发周期。
+FlowGram 提供了以下核心功能:
+
+- **流程画布**:提供可视化的节点、边的编排能力,同时支持自由布局和固定布局,可以轻松构建复杂的流程图。
+- **表单**:表单引擎维护节点数据的增删查改,并提供渲染、校验、副作用、联动、错误捕获等能力,简化了节点配置的开发。
+- **变量**:变量引擎支持作用域约束、变量结构透视、类型推导等能力,方便管理流程中的数据流转。
+- **物料**:提供开箱即用的组件、副作用、校验器等物料,开发者可以快速复用和扩展,提升开发效率。
+
+借助这些功能,开发者可以将精力聚焦于业务逻辑的实现,从而快速构建出功能完善、性能卓越的 AI 工作流平台。
+
+## 下一步
+
+请阅读 [快速上手](/guide/getting-started/quick-start) 来开始使用 FlowGram。
+
+欢迎到通过 [注册飞书](https://www.feishu.cn/en/) 并扫描下边的二维码加入飞书群,来与我们交流
+
+<img src="/lark-group.png" width="200"/>
+
+## 附:交互体验
+
+FlowGram 提供一套交互的最佳实践,让操作流程更加丝滑
+
+<table className="rs-table">
+  <tr>
+    <td>Motion 过渡动画</td>
+    <td>
+      <p>
+        Motion 动画在 Web 端应用可追溯到 Material Design,里边提到元素的变化如宽高或位置需要一个过渡过程,画布引擎会把线条和节点拆分单独绘制,使实现 Motion 过渡动画成本大大降低
+      </p>
+      <div className="rs-center">
+        <img loading="lazy" src="/common/motion.gif" />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>触摸板手势缩放 + 空格自由拖动画布</td>
+    <td>
+      <p>
+        手势指在 Mac 触摸板两指展开/合并可以实现画布放大/缩小,或者按住空格拖动画布,交互借鉴 Sketch、Figma
+      </p>
+      <div className="rs-center">
+        <img loading="lazy" src="/common/touch-pad.gif"  />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>缩略图</td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/minimap.gif"  />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>撤销/重做</td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/redo-undo.gif"  />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>复制/粘贴(支持快捷键)</td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/copypaste.gif"  />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <div>
+        <div>框选 + 拖拽</div>
+        <div>(固定)</div>
+      </div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <div className="rs-center">
+          <img loading="lazy" src="/fixed-layout/dragdrop.gif"  />
+        </div>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <div>水平/垂直布局切换</div>
+      <div>(固定)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/layout-change.gif"  />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <div>分支折叠</div>
+      <div>(固定)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/fold.gif"  />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <div>分组</div>
+      <div>(固定)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/fixed-layout/group.gif"  />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      自动整理
+      <div>(自由)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/free-layout/autolayout.gif"  />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      吸附对齐 + 参考线
+      <div>(自由)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/free-layout/snap.gif"  />
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      Coze Loop 子画布
+      <div>(自由)</div>
+    </td>
+    <td>
+      <div className="rs-center">
+        <img loading="lazy" src="/free-layout/loop.gif"  />
+      </div>
+    </td>
+  </tr>
+</table>

+ 100 - 0
apps/docs/src/zh/guide/getting-started/quick-start.mdx

@@ -0,0 +1,100 @@
+# 快速上手
+
+import {
+  PackageManagerTabs
+  // @ts-ignore
+} from '@theme';
+
+:::info
+快速体验 FlowGram.AI,你可以直接 [在 CodeSandbox 中打开](https://codesandbox.io/p/github/louisyoungx/flowgram-demo/main)
+:::
+
+选择开始方式:
+- 方式一:使用官方模板脚手架搭建新项目(⭐️ 推荐用于快速入门)。
+- 方式二:通过安装编辑器包集成到现有项目中。
+
+## 方式一:通过官方模版创建 FlowGram.AI 应用
+
+1. 使用 FlowGram CLI 搭建一个可运行的演示
+
+<PackageManagerTabs command={{
+  npm: "npx @flowgram.ai/create-app@latest",
+  pnpm: "pnpm dlx @flowgram.ai/create-app@latest",
+  yarn: "yarn dlx @flowgram.ai/create-app@latest",
+  bun: "bunx @flowgram.ai/create-app@latest",
+}} />
+
+2. 在提示时选择一个模板(推荐选择 `Free Layout Demo` 用于快速入门)
+
+```text
+- Free Layout Demo            # 自由布局最佳实践 (⭐️ 推荐)
+- Free Layout Demo Simple     # 自由布局基本用法
+- Fixed Layout Demo           # 固定布局最佳实践
+- Fixed Layout Demo Simple    # 固定布局基本用法
+```
+
+<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(320px, 1fr))', gap: 16, marginTop: 12 }}>
+  <div>
+    <p><strong>Free Layout Demo</strong> [查看在线演示](/examples/free-layout/free-layout-simple.html)</p>
+    <img src="/examples/example-free-layout.png" alt="自由布局预览" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+  <div>
+    <p><strong>Fixed Layout Demo</strong> [查看在线演示](/examples/fixed-layout/fixed-layout-simple.html)</p>
+    <img src="/examples/example-fixed-layout.png" alt="固定布局预览" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+  <div>
+    <p><strong>Free Layout Demo Simple</strong> [查看在线演示](/examples/free-layout/free-layout-simple.html)</p>
+    <img src="/examples/example-free-layout-simple.png" alt="自由布局简单预览" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+  <div>
+    <p><strong>Fixed Layout Demo Simple</strong> [查看在线演示](/examples/fixed-layout/fixed-layout-simple.html)</p>
+    <img src="/examples/example-fixed-layout-simple.png" alt="固定布局简单预览" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+</div>
+
+3. 查看安装的目录名称
+
+- 使用 Free Layout Demo 模板创建的项目,目录名称为 `demo-free-layout`
+- 使用 Free Layout Demo Simple 模板创建的项目,目录名称为 `demo-free-layout-simple`
+- 使用 Fixed Layout Demo 模板创建的项目,目录名称为 `demo-fixed-layout`
+- 使用 Fixed Layout Demo Simple 模板创建的项目,目录名称为 `demo-fixed-layout-simple`
+
+4. 进入项目目录
+
+```sh
+cd [project-name]
+```
+
+5. 启动开发服务器
+
+<PackageManagerTabs command={{
+  npm: "npm run dev",
+  pnpm: "pnpm dev",
+  yarn: "yarn dev",
+  bun: "bun dev",
+}} />
+
+## 方式二:直接安装编辑器包
+
+:::tip
+此方法适用于对 FlowGram 项目有一定了解的开发者。
+
+如果初次接触 FlowGram,我们建议优先选择方式一,先对 FlowGram 项目进行熟悉,然后再将所需代码逐步整合到现有的工程中。
+:::
+
+如果你需要将包添加到现有项目中,选择一个布局类型:
+
+<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
+  <div>
+    <strong>自由布局</strong>
+    <p>节点可以在画布上任意拖动,可以使用边在节点之间进行连接,从而建立节点之间的逻辑关系</p>
+    <p>下一步:[创建自由布局画布](/guide/getting-started/free-layout)</p>
+    <img src="/free-layout/free-layout-demo.gif" alt="自由布局演示" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+  <div>
+    <strong>固定布局</strong>
+    <p>节点在图中的位置代表了节点之间的逻辑关系</p>
+    <p>下一步:[创建固定布局画布](/guide/getting-started/fixed-layout)</p>
+    <img src="/fixed-layout/fixed-layout-demo.gif" alt="固定布局演示" style={{ width: '100%', borderRadius: 8 }} />
+  </div>
+</div>

+ 0 - 226
apps/docs/src/zh/guide/introduction.mdx

@@ -1,226 +0,0 @@
-# 介绍
-
-FlowGram 是一套基于节点编辑的流程搭建引擎,帮助开发者快速创建固定布局或自由连线布局模式的流程,并提供一套交互的最佳实践, 很适合有明确输入和输出的可视化工作流。
-
-在 AI 如火如荼的当下,我们也会更专注于如何让流程赋能 AI,为此特意加上 AI 后缀。
-
-<div className="rs-highlight">
-  FlowGram = Flow + Program,寓意流程如程序一样,拥有 Condition、Loop 甚至 TryCatch 节点。
-</div>
-
-
-## 官方 Demo
-
-
-<div style={{marginTop: 16, display: 'flex', gap: 8 }}>
-  <div>
-    <div>
-      <a className="rs-link" href="/examples/fixed-layout/fixed-feature-overview.html">
-        固定布局
-      </a>
-    </div>
-    <div className="rs-tip" style={{ height: 54 }}>
-      固定的排版,节点/分支支持指定位置拖拽移动,并提供分支、循环等复合节点
-    </div>
-    <div>
-      <img loading="lazy" src="/fixed-layout/fixed-layout-demo.gif"/>
-    </div>
-  </div>
-  <div>
-    <div>
-      <a className="rs-link" href="/examples/free-layout/free-feature-overview.html">
-        自由连线布局
-      </a>
-    </div>
-    <div className="rs-tip" style={{ height: 54 }}>
-      自由的排版,节点支持任意位置移动,通过自由连线连接节点
-    </div>
-    <div>
-      <img loading="lazy" src="/free-layout/free-layout-demo.gif"/>
-    </div>
-  </div>
-</div>
-
-## 交互体验
-
-提供一套交互的最佳实践,让操作流程更加丝滑
-
-<table className="rs-table">
-  <tr>
-    <td>Motion 过渡动画</td>
-    <td>
-      <p>
-        Motion 动画在 Web 端应用可追溯到 Material Design,里边提到元素的变化如宽高或位置需要一个过渡过程,画布引擎会把线条和节点拆分单独绘制,使实现 Motion 过渡动画成本大大降低
-      </p>
-      <div className="rs-center">
-        <img loading="lazy" src="/common/motion.gif" />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>触摸板手势缩放 + 空格自由拖动画布</td>
-    <td>
-      <p>
-        手势指在 Mac 触摸板两指展开/合并可以实现画布放大/缩小,或者按住空格拖动画布,交互借鉴 Sketch、Figma
-      </p>
-      <div className="rs-center">
-        <img loading="lazy" src="/common/touch-pad.gif"  />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>缩略图</td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/minimap.gif"  />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>撤销/重做</td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/redo-undo.gif"  />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>复制/粘贴(支持快捷键)</td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/copypaste.gif"  />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      <div>
-        <div>框选 + 拖拽</div>
-        <div>(固定)</div>
-      </div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <div className="rs-center">
-          <img loading="lazy" src="/fixed-layout/dragdrop.gif"  />
-        </div>
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      <div>水平/垂直布局切换</div>
-      <div>(固定)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/layout-change.gif"  />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      <div>分支折叠</div>
-      <div>(固定)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/fold.gif"  />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      <div>分组</div>
-      <div>(固定)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/fixed-layout/group.gif"  />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      自动整理
-      <div>(自由)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/free-layout/autolayout.gif"  />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      吸附对齐 + 参考线
-      <div>(自由)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/free-layout/snap.gif"  />
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      Coze Loop 子画布
-      <div>(自由)</div>
-    </td>
-    <td>
-      <div className="rs-center">
-        <img loading="lazy" src="/free-layout/loop.gif"  />
-      </div>
-    </td>
-  </tr>
-</table>
-
-## 线上应用
-
-<div style={{marginTop: 16, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
-  <div style={{flex: '0 0 30%', boxSizing: 'border-box'}}>
-    <div>
-      <a className="rs-link" href="https://www.coze.cn/open/docs/guides/workflow" target="_blank">
-        扣子工作流
-      </a>
-    </div>
-    <div>
-      <img loading="lazy" src="/ref-coze.png"/>
-    </div>
-  </div>
-  <div style={{flex: '0 0 30%', boxSizing: 'border-box'}}>
-    <a className="rs-link" href="https://ae.feishu.cn/hc/zh-CN/articles/120610822514" target="_blank" >
-      飞书低代码平台工作流
-    </a>
-    <div>
-      <img loading="lazy" src="/ref-apaas.png"/>
-    </div>
-  </div>
-  <div style={{flex: '0 0 30%', boxSizing: 'border-box'}}>
-    <a className="rs-link" href="https://www.feishu.cn/hc/zh-CN/articles/908751305974-%E4%BB%80%E4%B9%88%E6%98%AF%E5%A4%9A%E7%BB%B4%E8%A1%A8%E6%A0%BC%E5%B7%A5%E4%BD%9C%E6%B5%81" target="_blank" >
-      飞书多维表格工作流
-    </a>
-    <div>
-      <img loading="lazy" src="/ref-bitable.png"/>
-    </div>
-  </div>
-  <div style={{flex: '0 0 30%', boxSizing: 'border-box'}}>
-    <a className="rs-link" href="https://github.com/NNDeploy/nndeploy" target="_blank" >
-      nndeploy
-    </a>
-    <div>
-      <img loading="lazy" src="/ref-nndeploy.png"/>
-    </div>
-  </div>
-  <div style={{flex: '0 0 30%', boxSizing: 'border-box'}}>
-    <a className="rs-link" href="https://github.com/certimate-go/certimate" target="_blank" >
-      Certimate
-    </a>
-    <div>
-      <img loading="lazy" src="/ref-certimate.png"/>
-    </div>
-  </div>
-</div>
-
-
-

+ 4 - 4
apps/docs/src/zh/index.md

@@ -3,17 +3,17 @@ pageType: home
 
 hero:
   name: FlowGram.AI
-  text: 插件化构建流程引擎
-  tagline: 高性能,可扩展,可定制
+  text: 工作流开发框架
+  tagline: 让搭建工作流平台更简单:画布、表单、变量、物料
   actions:
     - theme: brand
       text: 快速开始
-      link: /guide/introduction
+      link: /guide/getting-started/introduction
     - theme: alt
       text: GitHub
       link: https://github.com/bytedance/flowgram.ai
   image:
-    src: /logo.png
+    src: /transparent-logo.svg
     alt: Logo
 features:
   - title: 扣子

+ 39 - 0
apps/docs/theme/components/background/index.css

@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+.background2-canvas {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+  z-index: -1;
+}
+
+/* Ensure smooth rendering */
+.background2-canvas {
+  image-rendering: -webkit-optimize-contrast;
+  image-rendering: -moz-crisp-edges;
+  image-rendering: crisp-edges;
+  image-rendering: pixelated;
+}
+
+/* Dark mode specific adjustments */
+[data-theme='dark'] .background2-canvas {
+  opacity: 0.9;
+}
+
+/* Light mode specific adjustments */
+[data-theme='light'] .background2-canvas {
+  opacity: 0.8;
+}
+
+/* Animation performance optimization */
+.background2-canvas {
+  will-change: transform;
+  transform: translateZ(0);
+  backface-visibility: hidden;
+}

+ 604 - 0
apps/docs/theme/components/background/index.tsx

@@ -0,0 +1,604 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
+
+import { useDark } from '@rspress/core/runtime';
+import './index.css';
+
+// Performance configuration based on device capabilities
+interface PerformanceConfig {
+  enabled: boolean;
+  meteorCount: number;
+  maxFlameTrails: number;
+  trailLength: number;
+  animationQuality: 'high' | 'medium' | 'low';
+  frameSkip: number;
+}
+
+// Performance detection utilities
+const detectPerformance = (): PerformanceConfig => {
+  // Check for reduced motion preference
+  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
+    return {
+      enabled: false,
+      meteorCount: 0,
+      maxFlameTrails: 0,
+      trailLength: 0,
+      animationQuality: 'low',
+      frameSkip: 0,
+    };
+  }
+
+  // Basic device capability detection
+  const canvas = document.createElement('canvas');
+  const ctx = canvas.getContext('2d');
+  if (!ctx) {
+    return {
+      enabled: false,
+      meteorCount: 0,
+      maxFlameTrails: 0,
+      trailLength: 0,
+      animationQuality: 'low',
+      frameSkip: 0,
+    };
+  }
+
+  // Check hardware concurrency (CPU cores)
+  const cores = navigator.hardwareConcurrency || 2;
+
+  // Check memory (if available)
+  const memory = (navigator as any).deviceMemory || 4;
+
+  // Check if mobile device
+  const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
+    navigator.userAgent
+  );
+
+  // Performance scoring
+  let score = 0;
+  score += cores >= 4 ? 2 : cores >= 2 ? 1 : 0;
+  score += memory >= 8 ? 2 : memory >= 4 ? 1 : 0;
+  score += isMobile ? -1 : 1;
+
+  // Configure based on performance score
+  if (score >= 4) {
+    // High performance
+    return {
+      enabled: true,
+      meteorCount: 12,
+      maxFlameTrails: 15,
+      trailLength: 20,
+      animationQuality: 'high',
+      frameSkip: 1,
+    };
+  } else if (score >= 2) {
+    // Medium performance
+    return {
+      enabled: true,
+      meteorCount: 8,
+      maxFlameTrails: 8,
+      trailLength: 12,
+      animationQuality: 'medium',
+      frameSkip: 2,
+    };
+  } else {
+    // Low performance - disable
+    return {
+      enabled: false,
+      meteorCount: 0,
+      maxFlameTrails: 0,
+      trailLength: 0,
+      animationQuality: 'low',
+      frameSkip: 0,
+    };
+  }
+};
+
+// Trail particle interface for flame effect
+interface TrailParticle {
+  x: number;
+  y: number;
+  vx: number;
+  vy: number;
+  size: number;
+  tick: number;
+  life: number;
+  alpha: number;
+  color: string;
+}
+
+// Meteor particle interface definition
+interface MeteorParticle {
+  x: number;
+  y: number;
+  vx: number;
+  vy: number;
+  radius: number;
+  color: string;
+  alpha: number;
+  trail: Array<{ x: number; y: number; alpha: number }>;
+  trailLength: number;
+  speed: number;
+  angle: number;
+  // New flame trail system
+  flameTrails: TrailParticle[];
+  maxFlameTrails: number;
+}
+
+// Configuration options for flame effects
+const flameOptions = {
+  trailSizeBaseMultiplier: 0.6,
+  trailSizeAddedMultiplier: 0.3,
+  trailSizeSpeedMultiplier: 0.15,
+  trailAddedBaseRadiant: -0.8,
+  trailAddedAddedRadiant: 3,
+  trailBaseLifeSpan: 25,
+  trailAddedLifeSpan: 20,
+  trailGenerationChance: 0.3,
+};
+
+// Trail class for managing individual flame particles
+class Trail {
+  private particle: TrailParticle;
+
+  private parentMeteor: MeteorParticle;
+
+  constructor(parent: MeteorParticle) {
+    this.parentMeteor = parent;
+    this.particle = this.createTrailParticle();
+  }
+
+  // Create a new trail particle based on parent meteor
+  private createTrailParticle = (): TrailParticle => {
+    const baseSize =
+      this.parentMeteor.radius *
+      (flameOptions.trailSizeBaseMultiplier +
+        flameOptions.trailSizeAddedMultiplier * Math.random());
+
+    const radiantOffset =
+      flameOptions.trailAddedBaseRadiant + flameOptions.trailAddedAddedRadiant * Math.random();
+    const trailAngle = this.parentMeteor.angle + radiantOffset;
+    const speed = baseSize * flameOptions.trailSizeSpeedMultiplier;
+
+    return {
+      x: this.parentMeteor.x + (Math.random() - 0.5) * this.parentMeteor.radius,
+      y: this.parentMeteor.y + (Math.random() - 0.5) * this.parentMeteor.radius,
+      vx: speed * Math.cos(trailAngle),
+      vy: speed * Math.sin(trailAngle),
+      size: baseSize,
+      tick: 0,
+      life: Math.floor(
+        flameOptions.trailBaseLifeSpan + flameOptions.trailAddedLifeSpan * Math.random()
+      ),
+      alpha: 0.8 + Math.random() * 0.2,
+      color: this.parentMeteor.color,
+    };
+  };
+
+  // Update trail particle position and lifecycle
+  public step = (): boolean => {
+    this.particle.tick++;
+
+    // Check if trail particle should be removed
+    if (this.particle.tick > this.particle.life) {
+      return false; // Signal for removal
+    }
+
+    // Update position
+    this.particle.x += this.particle.vx;
+    this.particle.y += this.particle.vy;
+
+    // Apply slight deceleration for more realistic flame behavior
+    this.particle.vx *= 0.98;
+    this.particle.vy *= 0.98;
+
+    return true; // Continue existing
+  };
+
+  // Render the trail particle
+  public draw = (ctx: CanvasRenderingContext2D): void => {
+    const lifeRatio = 1 - this.particle.tick / this.particle.life;
+    const currentSize = this.particle.size * lifeRatio;
+    const currentAlpha = this.particle.alpha * lifeRatio;
+
+    if (currentSize <= 0 || currentAlpha <= 0) return;
+
+    const alphaHex = Math.floor(currentAlpha * 255)
+      .toString(16)
+      .padStart(2, '0');
+
+    // Draw flame particle with gradient
+    const gradient = ctx.createRadialGradient(
+      this.particle.x,
+      this.particle.y,
+      0,
+      this.particle.x,
+      this.particle.y,
+      currentSize * 2
+    );
+
+    gradient.addColorStop(0, this.particle.color + 'FF');
+    gradient.addColorStop(0.4, this.particle.color + alphaHex);
+    gradient.addColorStop(0.8, this.particle.color + '33');
+    gradient.addColorStop(1, this.particle.color + '00');
+
+    ctx.beginPath();
+    ctx.arc(this.particle.x, this.particle.y, currentSize, 0, Math.PI * 2);
+    ctx.fillStyle = gradient;
+    ctx.fill();
+
+    // Add glow effect
+    ctx.shadowColor = this.particle.color;
+    ctx.shadowBlur = currentSize * 2;
+    ctx.fill();
+    ctx.shadowBlur = 0;
+  };
+}
+
+// Background2 component - Circular meteor with enhanced flame trailing effect
+export const Background: React.FC = () => {
+  const canvasRef = useRef<HTMLCanvasElement>(null);
+  const animationRef = useRef<number>(0);
+  const meteorsRef = useRef<MeteorParticle[]>([]);
+  const mouseRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
+  const frameCountRef = useRef<number>(0);
+  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
+  const isDark = useDark();
+
+  // Performance configuration - memoized to avoid recalculation
+  const performanceConfig = useMemo(() => detectPerformance(), []);
+
+  // Early return if animation is disabled
+  if (!performanceConfig.enabled) {
+    return null;
+  }
+
+  // Color configuration - adjusted based on theme mode
+  const lightColors = ['#4062A7', '#5482BE', '#5ABAC2', '#86C8C5'];
+  const darkColors = ['#6B8CFF', '#8DA9FF', '#7FDBDA', '#A8EDEA'];
+  const colors = isDark ? darkColors : lightColors;
+
+  // Initialize meteor particles with flame trail system - optimized based on performance
+  const initMeteors = useCallback((): void => {
+    meteorsRef.current = [];
+
+    for (let i = 0; i < performanceConfig.meteorCount; i++) {
+      const angle = Math.PI * 0.25; // Fixed 45 degrees (down-right)
+      const radius = Math.random() * 1.2 + 1.8; // Slightly larger meteors for better flame effect
+      const speed = (radius - 1.8) * 3.0 + 2.0; // Adjusted speed range
+      const trailLength = Math.floor(
+        Math.random() * (performanceConfig.trailLength * 0.5) + performanceConfig.trailLength * 0.5
+      );
+
+      meteorsRef.current.push({
+        x: Math.random() * dimensions.width,
+        y: Math.random() * dimensions.height,
+        vx: Math.cos(angle) * speed,
+        vy: Math.sin(angle) * speed,
+        radius,
+        color: colors[Math.floor(Math.random() * colors.length)],
+        alpha: Math.random() * 0.3 + 0.7,
+        trail: [],
+        trailLength,
+        speed,
+        angle,
+        // Initialize flame trail system with performance-based limits
+        flameTrails: [],
+        maxFlameTrails: Math.floor(
+          radius * performanceConfig.maxFlameTrails * 0.5 + performanceConfig.maxFlameTrails * 0.3
+        ),
+      });
+    }
+  }, [performanceConfig, dimensions.width, dimensions.height, colors]);
+
+  // Update meteor trail (original trail system) - optimized
+  const updateTrail = useCallback(
+    (meteor: MeteorParticle): void => {
+      meteor.trail.unshift({
+        x: meteor.x,
+        y: meteor.y,
+        alpha: meteor.alpha,
+      });
+
+      if (meteor.trail.length > meteor.trailLength) {
+        meteor.trail.pop();
+      }
+
+      // Only update alpha for visible trail points in high quality mode
+      if (performanceConfig.animationQuality === 'high') {
+        meteor.trail.forEach((point, index) => {
+          point.alpha = meteor.alpha * (1 - index / meteor.trailLength);
+        });
+      }
+    },
+    [performanceConfig.animationQuality]
+  );
+
+  // Update flame trails system - optimized
+  const updateFlameTrails = useCallback(
+    (meteor: MeteorParticle): void => {
+      // Reduce flame trail generation based on performance config
+      const generationChance =
+        performanceConfig.animationQuality === 'high'
+          ? flameOptions.trailGenerationChance
+          : performanceConfig.animationQuality === 'medium'
+          ? flameOptions.trailGenerationChance * 0.7
+          : flameOptions.trailGenerationChance * 0.4;
+
+      // Generate new flame trail particles
+      if (meteor.flameTrails.length < meteor.maxFlameTrails && Math.random() < generationChance) {
+        const trail = new Trail(meteor);
+        meteor.flameTrails.push(trail as any); // Type assertion for compatibility
+      }
+
+      // Update existing flame trails and remove expired ones
+      meteor.flameTrails = meteor.flameTrails.filter((trail: any) => trail.step && trail.step());
+    },
+    [performanceConfig.animationQuality]
+  );
+
+  // Draw meteor with enhanced flame trailing effect - optimized
+  const drawMeteor = useCallback(
+    (ctx: CanvasRenderingContext2D, meteor: MeteorParticle): void => {
+      // Draw flame trails first (behind the meteor) - skip in low quality mode
+      if (performanceConfig.animationQuality !== 'low') {
+        meteor.flameTrails.forEach((trail: any) => {
+          if (trail.draw) {
+            trail.draw(ctx);
+          }
+        });
+      }
+
+      // Draw original trail system with reduced complexity for lower quality
+      const trailStep =
+        performanceConfig.animationQuality === 'high'
+          ? 1
+          : performanceConfig.animationQuality === 'medium'
+          ? 2
+          : 3;
+
+      for (let i = 0; i < meteor.trail.length; i += trailStep) {
+        const point = meteor.trail[i];
+        const trailRadius = meteor.radius * (1 - i / meteor.trail.length) * 0.8;
+        const alpha =
+          performanceConfig.animationQuality === 'high'
+            ? point.alpha
+            : meteor.alpha * (1 - i / meteor.trail.length);
+
+        const alphaHex = Math.floor(alpha * 255)
+          .toString(16)
+          .padStart(2, '0');
+
+        ctx.beginPath();
+        ctx.arc(point.x, point.y, trailRadius, 0, Math.PI * 2);
+        ctx.fillStyle = meteor.color + alphaHex;
+        ctx.fill();
+
+        // Reduce shadow effects for better performance
+        if (performanceConfig.animationQuality === 'high') {
+          ctx.shadowColor = meteor.color;
+          ctx.shadowBlur = trailRadius * 2 + meteor.radius;
+          ctx.fill();
+          ctx.shadowBlur = 0;
+        }
+      }
+
+      // Draw main meteor body
+      ctx.beginPath();
+      ctx.arc(meteor.x, meteor.y, meteor.radius, 0, Math.PI * 2);
+
+      // Simplified gradient for lower quality modes
+      if (performanceConfig.animationQuality === 'high') {
+        const gradient = ctx.createRadialGradient(
+          meteor.x,
+          meteor.y,
+          0,
+          meteor.x,
+          meteor.y,
+          meteor.radius * 2.5
+        );
+        gradient.addColorStop(0, meteor.color + 'FF');
+        gradient.addColorStop(0.5, meteor.color + 'DD');
+        gradient.addColorStop(0.8, meteor.color + '77');
+        gradient.addColorStop(1, meteor.color + '00');
+        ctx.fillStyle = gradient;
+      } else {
+        ctx.fillStyle = meteor.color + 'DD';
+      }
+
+      ctx.fill();
+
+      // Add bright core
+      ctx.beginPath();
+      ctx.arc(meteor.x, meteor.y, meteor.radius * 0.7, 0, Math.PI * 2);
+      ctx.fillStyle = meteor.color + 'FF';
+      ctx.fill();
+
+      // Enhanced outer glow - only in high quality mode
+      if (performanceConfig.animationQuality === 'high') {
+        ctx.shadowColor = meteor.color;
+        ctx.shadowBlur = meteor.radius * 5 + 3;
+        ctx.fill();
+        ctx.shadowBlur = 0;
+      }
+    },
+    [performanceConfig.animationQuality]
+  );
+
+  // Update meteor position and behavior
+  const updateMeteor = (meteor: MeteorParticle): void => {
+    // Mouse interaction - meteors are slightly attracted to mouse
+    const dx = mouseRef.current.x - meteor.x;
+    const dy = mouseRef.current.y - meteor.y;
+    const distance = Math.sqrt(dx * dx + dy * dy);
+
+    if (distance < 120) {
+      const force = ((120 - distance) / 120) * 0.008; // Slightly reduced force for stability
+      const angle = Math.atan2(dy, dx);
+      meteor.vx += Math.cos(angle) * force;
+      meteor.vy += Math.sin(angle) * force;
+    }
+
+    // Update both trail systems before moving
+    updateTrail(meteor);
+    updateFlameTrails(meteor);
+
+    // Update position
+    meteor.x += meteor.vx;
+    meteor.y += meteor.vy;
+
+    // Boundary wrapping with smooth transition
+    if (meteor.x > dimensions.width + meteor.radius * 3) {
+      meteor.x = -meteor.radius * 3;
+      meteor.trail = []; // Clear trail when wrapping
+      meteor.flameTrails = []; // Clear flame trails when wrapping
+    }
+    if (meteor.x < -meteor.radius * 3) {
+      meteor.x = dimensions.width + meteor.radius * 3;
+      meteor.trail = [];
+      meteor.flameTrails = [];
+    }
+    if (meteor.y > dimensions.height + meteor.radius * 3) {
+      meteor.y = -meteor.radius * 3;
+      meteor.trail = [];
+      meteor.flameTrails = [];
+    }
+    if (meteor.y < -meteor.radius * 3) {
+      meteor.y = dimensions.height + meteor.radius * 3;
+      meteor.trail = [];
+      meteor.flameTrails = [];
+    }
+
+    // Maintain consistent direction - gently guide back to base direction
+    const baseAngle = Math.PI * 0.25; // 45 degrees
+
+    // Gradually adjust direction
+    meteor.vx += Math.cos(baseAngle) * 0.003;
+    meteor.vy += Math.sin(baseAngle) * 0.003;
+
+    // Apply slight damping to prevent excessive speed
+    meteor.vx *= 0.999;
+    meteor.vy *= 0.999;
+
+    // Maintain minimum speed to keep meteors moving
+    const currentSpeed = Math.sqrt(meteor.vx * meteor.vx + meteor.vy * meteor.vy);
+    if (currentSpeed < 0.5) {
+      meteor.vx = Math.cos(baseAngle) * 0.8;
+      meteor.vy = Math.sin(baseAngle) * 0.8;
+    }
+
+    // Update meteor angle for flame trail generation
+    meteor.angle = Math.atan2(meteor.vy, meteor.vx);
+  };
+
+  // Animation loop - optimized with frame skipping
+  const animate = useCallback((): void => {
+    const canvas = canvasRef.current;
+    if (!canvas) return;
+
+    const ctx = canvas.getContext('2d');
+    if (!ctx) return;
+
+    // Frame skipping for performance optimization
+    frameCountRef.current++;
+    if (frameCountRef.current % performanceConfig.frameSkip !== 0) {
+      animationRef.current = requestAnimationFrame(animate);
+      return;
+    }
+
+    // Clear canvas completely to avoid permanent trails
+    ctx.clearRect(0, 0, dimensions.width, dimensions.height);
+
+    // Add subtle background with very low opacity for better flame visibility
+    // Skip background overlay in low quality mode
+    if (performanceConfig.animationQuality !== 'low') {
+      const bgColor = isDark ? 'rgba(13, 17, 23, 0.015)' : 'rgba(237, 243, 248, 0.015)';
+      ctx.fillStyle = bgColor;
+      ctx.fillRect(0, 0, dimensions.width, dimensions.height);
+    }
+
+    // Update and draw meteors
+    meteorsRef.current.forEach((meteor) => {
+      updateMeteor(meteor);
+      drawMeteor(ctx, meteor);
+    });
+
+    animationRef.current = requestAnimationFrame(animate);
+  }, [performanceConfig, dimensions, isDark, updateMeteor, drawMeteor]);
+
+  // Handle window resize - optimized
+  useEffect(() => {
+    const handleResize = (): void => {
+      setDimensions({
+        width: window.innerWidth,
+        height: window.innerHeight,
+      });
+    };
+
+    handleResize();
+    window.addEventListener('resize', handleResize);
+    return () => window.removeEventListener('resize', handleResize);
+  }, []);
+
+  // Handle mouse movement - optimized with throttling
+  useEffect(() => {
+    let throttleTimer: NodeJS.Timeout | null = null;
+
+    const handleMouseMove = (e: MouseEvent): void => {
+      if (throttleTimer) return;
+
+      throttleTimer = setTimeout(
+        () => {
+          mouseRef.current = { x: e.clientX, y: e.clientY };
+          throttleTimer = null;
+        },
+        performanceConfig.animationQuality === 'high'
+          ? 16
+          : performanceConfig.animationQuality === 'medium'
+          ? 32
+          : 64
+      );
+    };
+
+    window.addEventListener('mousemove', handleMouseMove);
+    return () => {
+      window.removeEventListener('mousemove', handleMouseMove);
+      if (throttleTimer) {
+        clearTimeout(throttleTimer);
+      }
+    };
+  }, [performanceConfig.animationQuality]);
+
+  // Initialize and start animation - optimized
+  useEffect(() => {
+    if (dimensions.width === 0 || dimensions.height === 0) return;
+
+    initMeteors();
+    animate();
+
+    return () => {
+      if (animationRef.current) {
+        cancelAnimationFrame(animationRef.current);
+      }
+    };
+  }, [dimensions, isDark, initMeteors, animate]);
+
+  return (
+    <canvas
+      ref={canvasRef}
+      width={dimensions.width}
+      height={dimensions.height}
+      className="background2-canvas"
+      style={{
+        position: 'absolute',
+        width: '100%',
+        height: '100%',
+        pointerEvents: 'none',
+        zIndex: -1,
+      }}
+    />
+  );
+};

+ 50 - 0
apps/docs/theme/components/logo/index.less

@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+.flowgram-logo-container {
+  position: absolute;
+  top: 50px;
+  width: calc(30vw - 64px);
+  height: 550px;
+
+  // Mobile responsive: move to top on small screens to prevent text overlap
+  @media (max-width: 768px) {
+    top: 0;
+    right: 0;
+    width: 100%;
+    height: 200px;
+    margin-bottom: 20px;
+  }
+
+  // Tablet responsive: adjust size and position
+  @media (min-width: 769px) and (max-width: 1024px) {
+    width: calc(45vw - 32px);
+    right: 16px;
+  }
+
+  // Ensure minimum width to prevent squashing
+  @media (min-width: 1025px) and (max-width: 1300px) {
+    width: calc(45vw - 32px);
+    right: 0;
+  }
+
+  // Ensure minimum width to prevent squashing
+  @media (min-width: 1301) {
+    width: calc(30vw - 64px);
+    right: 15vw;
+  }
+}
+
+.gedit-playground {
+  background: transparent !important;
+}
+
+.gedit-playground-scroll-right-block {
+  display: none;
+}
+
+.gedit-playground-scroll-bottom-block {
+  display: none;
+}

+ 23 - 0
apps/docs/theme/components/logo/index.tsx

@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import '@flowgram.ai/free-layout-editor/index.css';
+
+import { FreeLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/free-layout-editor';
+
+import './index.less';
+
+import { useEditorProps } from './use-editor-props';
+
+export const FlowGramLogo = () => {
+  const editorProps = useEditorProps();
+  return (
+    <div className="flowgram-logo-container">
+      <FreeLayoutEditorProvider {...editorProps}>
+        <EditorRenderer />
+      </FreeLayoutEditorProvider>
+    </div>
+  );
+};

+ 53 - 0
apps/docs/theme/components/logo/initial-data.ts

@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
+
+export const initialData: WorkflowJSON = {
+  nodes: [
+    {
+      id: '1',
+      type: 'start',
+      meta: {
+        position: { x: 0, y: 0 },
+      },
+    },
+    {
+      id: '2',
+      type: 'custom',
+      meta: {
+        position: { x: 110, y: 0 },
+      },
+    },
+    {
+      id: '3',
+      type: 'custom',
+      meta: {
+        position: { x: 220, y: 0 },
+      },
+    },
+    {
+      id: '4',
+      type: 'end',
+      meta: {
+        position: { x: 330, y: 0 },
+      },
+    },
+  ],
+  edges: [
+    {
+      sourceNodeID: '1',
+      targetNodeID: '2',
+    },
+    {
+      sourceNodeID: '2',
+      targetNodeID: '3',
+    },
+    {
+      sourceNodeID: '3',
+      targetNodeID: '4',
+    },
+  ],
+};

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff