Nguồn:
.kiro/specs/cyclic-editor-nesting/design.md(design chính thức) +nested-config-infinite-nesting-research.md(research/thảo luận ban đầu). Tài liệu này không lặp lại toàn bộ design, chỉ trực quan hóa 3 khía cạnh người đọc cần so sánh nhanh: (1) config MFE module, (2) config setting riêng theo use case cho mỗi nested editor, (3) cách start editor nested theo kiểu lazy.
config.server)subSpecs, inline toàn câyeditors() lặp mfeReq.specs; creator word/composer đọc settings.embedded,
rồi với mỗi item đệ quy gọi lại creator của con và inline toàn bộ kết quả (kể cả
nestedCoordConf.edLookups của con) vào node cha. Với word ⊃ composer một chiều thì
dừng được vì composer cố tình bỏ word khỏi embedded — nhưng đây chính là cái giới hạn
cần bỏ.
Đặc điểm:
impl + nestedCoordConf của con.expandEditorsClosure dùng worklist + visited-set trên editor type (không phải trên
vị trí trong cây). Mỗi type được xử lý đúng 1 lần; creator không còn đệ quy — chỉ
emit node của chính mình + danh sách con theo tên. Con được enqueue vào worklist,
không inline.
Registry 2 tầng (điểm mấu chốt để so sánh):
| CŨ | MỚI | |
|---|---|---|
| Đơn vị payload | 1 cây lồng sâu (impl + nestedCoordConf inline) |
1 node / type reachable (module + childBindings 1 cấp) |
| Chặn chu trình | Luật một chiều thủ công (composer bỏ word) |
visited trên type — tự nhiên hữu hạn, cho phép chu trình |
settings.embedded thiếu |
for...of undefined → crash |
safeEmbedded() → "không nhúng gì" |
| Cache/dedup theo type | Không — con lặp lại bị inline nhiều lần | Có — mỗi type đúng 1 node |
| Kích thước payload | Tăng theo độ sâu cây (branching^depth) |
Chặn trên bởi số editor type (hằng số) |
Đây là phần dễ hiểu nhầm khi "phẳng hóa" config: câu hỏi cốt lõi là làm sao vẫn giữ được
word → math (LOCAL) khác composer → math (GLOBAL) khi không còn inline theo cây.
Vì cây được inline hoàn toàn, "context" (cha nào đang nhúng) được thể hiện tự nhiên bằng
vị trí trong cây — node con nằm dưới word mang settings khác node con (cùng type)
nằm dưới composer, vì chúng là hai bản inline riêng biệt.
→ Đúng về mặt ngữ nghĩa, nhưng cái giá phải trả là mỗi lần "math" xuất hiện dưới một cha khác, toàn bộ subtree bị nhân bản → đây chính là nguồn phình payload / đệ quy vô tận khi có chu trình.
Node math trong registry chỉ có 1 bản (defaults — context-độc-lập). Context-dependent
settings (LOCAL vs GLOBAL) được lưu ở childBindings của node cha — tức là trên
cạnh cha→con, không phải trên node con.
Công thức resolve khi materialize (client, EditorConfigRegistry.resolveChildConfig):
settings := merge(registry[math].defaults,
registry[Parent].childBindings[math].settingsOverride)
→ word materialize math → merge với childBindings của word; composer
materialize math → merge với childBindings của composer. Cùng 1 node math trong
registry, nhưng 2 lần materialize (2 instance riêng, vì editor không share giữa các tầng)
nhận 2 settings khác nhau — đúng yêu cầu, không cần nhân bản node.
| CŨ | MỚI | |
|---|---|---|
| Nơi lưu context settings | Trên node con (do bị nhân bản theo cha lúc inline) | Trên cạnh cha→con (childBindings của node cha) |
| Số bản của 1 type trong payload | N bản (N = số cha khác nhau từng nhúng nó) | 1 bản duy nhất (defaults) trong registry |
| Cách tính settings cuối | Đã tính sẵn trong lúc server inline | merge(child.defaults, parent.childBindings[child].override) — tính lúc client materialize |
| Module (remote) | Lặp lại theo mỗi bản inline | 1 bản (EditorModuleLookup), dùng chung cho mọi context — dedup theo Module Federation |
| Phạm vi context | Không giới hạn rõ — ngầm định theo cây | Rõ ràng: context = immediate parent type (1 cấp); sâu hơn (path-dependent) ngoài scope hiện tại |
initialize()Gateway khi thiếu channel (type chưa từng được add, hoặc lỗi thứ tự) thì:
initialize() không còn loop tạo instance — chỉ đăng ký LazyEditorProvider +
channel gateway. Instance chỉ được tạo khi thực sự bị truy cập (editorByType,
routing lệnh, load/insert/paste).
Truy cập lần đầu → construct đồng bộ → init/start nền:
Không còn drop lệnh — gateway tự materialize khi thiếu channel:
Đường bulk (load/paste cây dựng sẵn) — pre-scan rồi materialize tất cả trước khi apply:
| CŨ (eager) | MỚI (lazy) | |
|---|---|---|
| Khi nào tạo instance | Ngay trong initialize(), cho mọi type hỗ trợ |
Khi truy cập lần đầu (access/route/load/insert/paste) |
| Có xét nội dung tài liệu? | Không | Có — chỉ type thực sự dùng mới materialize |
| Với chu trình type (word↔composer) | 💥 construct dây chuyền vô tận | Dừng ngay — construct không chain, nestedCoord con khởi tạo rỗng |
| Channel/deserializer sẵn sàng khi nào | Cùng lúc với eager construct | Ngay trong resolve(), trước khi return — vẫn đồng bộ |
| Lệnh đến cho type chưa có channel | Drop im lặng → peer phân kỳ | ensureChannel materialize đồng bộ → buffer tới start(), không drop |
initialize()/start() của sub-editor |
Await tuần tự trong loop cha | Dispatch nền, không await trong resolve(); ensureStarted() riêng cho nơi cần đợi |
| Double-start / double-init | Không áp dụng (chỉ tạo 1 lần theo loop) | Chặn bằng lifecycles: Map<Type, {initialized, started}> — memoized, mỗi type init/start đúng 1 lần |
| Toolbar | Giả định mọi editor đã tồn tại, duyệt this.editors |
Dựng tăng dần theo event editor-added |
| Timeout an toàn cho caller | Không có khái niệm này | ensureStarted timeout 30s → reject, background init/start vẫn chạy tiếp nhưng caller đã abort |
| Khía cạnh | File |
|---|---|
| Config closure (server) | config.server/src/app.controller.ts (expandEditorsClosure), closure.helpers.ts (safeEmbedded, defaultEmbedSpec, indexSpecsByItem) |
| Data model 2 tầng | config.server/src/app.model.ts (EditorRegistryNode, ChildBinding, EditorModuleLookup) |
| Module registry (client) | editor.core/src/lib/registry/module.registry.ts (DefaultModuleRegistry) |
| Config registry (client, resolve theo context) | editor.core/src/lib/registry/config.registry.ts (DefaultEditorConfigRegistry) |
| Lazy provider (construct đồng bộ, init/start nền) | editor.nested/src/lib/coord/default.lazy.editor.provider.ts (DefaultLazyEditorProvider) |
| Nested coordinator (initialize không eager) | editor.nested/src/lib/coord/nested.coordinator.ts |
editorByType resolve-on-access |
editor.coordinator/common/src/base.coordinator.ts |
Gateway ensureChannel (không drop) |
editor.coordinator/classroom/src/cmd/default.cmd.gateway.ts, editor.nested/.../cmds/nested.wrapping.cmd.gateway.ts |
| Bulk pre-scan (load/paste) | editor.nested/src/lib/coord/bulk.prescan.ts |
| Design & requirements đầy đủ | .kiro/specs/cyclic-editor-nesting/{design.md,requirements.md,tasks.md} |
| Research/thảo luận ban đầu | document/config.server/nested-config-infinite-nesting-research.md |