@ssobig/writer-cli 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +11 -4
  2. package/asset-repository.js +7 -2
  3. package/package.json +1 -1
  4. package/templates/mystery-v1/codemirror6-runtime.min.js +1 -1
  5. package/templates/mystery-v1/component-asset-operations.js +14 -2
  6. package/templates/mystery-v1/component-field-contracts.js +98 -2
  7. package/templates/mystery-v1/component-navigation-counts.js +6 -1
  8. package/templates/mystery-v1/component-storage-contract.js +28 -9
  9. package/templates/mystery-v1/markdown-document-model.js +444 -0
  10. package/templates/mystery-v1/markdown-image-editor.js +320 -0
  11. package/templates/mystery-v1/markdown-live-editor.js +979 -105
  12. package/templates/mystery-v1/timeline-model.js +235 -0
  13. package/tools/writer-cli/package-lock.json +2 -2
  14. package/tools/writer-cli/package.json +1 -1
  15. package/tools/writer-cli/skills/ssobig-writer-cli/SKILL.md +12 -13
  16. package/tools/writer-cli/skills/ssobig-writer-cli/references/assets-checkpoints.md +5 -1
  17. package/tools/writer-cli/skills/ssobig-writer-cli/references/errors.md +4 -1
  18. package/tools/writer-cli/skills/ssobig-writer-cli/references/install-auth.md +3 -3
  19. package/tools/writer-cli/skills/ssobig-writer-cli/references/investigation-board.md +9 -0
  20. package/tools/writer-cli/skills/ssobig-writer-cli/references/layout-spec.md +130 -0
  21. package/tools/writer-cli/skills/ssobig-writer-cli/references/projects-components.md +7 -1
  22. package/tools/writer-cli/skills/ssobig-writer-cli/references/read-search.md +12 -2
  23. package/tools/writer-cli/src/command-registry.cjs +24 -23
  24. package/tools/writer-cli/src/commands.cjs +22 -1
  25. package/tools/writer-cli/src/domain.cjs +46 -0
  26. package/tools/writer-cli/src/project-import.cjs +15 -1
package/README.md CHANGED
@@ -20,13 +20,15 @@ CLI는 stdout에 JSON 문서 하나를 출력한다. 원고 변경은 `plan ->
20
20
  작품 구성 화면의 등록된 선택 Component 추가·사용 여부와 선택 속성도 같은 절차로 변경할 수 있다.
21
21
 
22
22
  ```bash
23
- ssobig-writer component plan-add --project <slug> --template ssobig.timeline --out component-add.plan.json
24
- ssobig-writer apply component-add.plan.json --receipt component-add.receipt.json
23
+ ssobig-writer component plan-add --project <slug> --template ssobig.timeline --out .ssobig-writer/component-add.plan.json
24
+ ssobig-writer apply .ssobig-writer/component-add.plan.json --receipt .ssobig-writer/component-add.receipt.json
25
25
 
26
- ssobig-writer component plan-set-active --project <slug> --instance timeline --active false --out component-archive.plan.json
27
- ssobig-writer component plan-set-field --project <slug> --instance clues --field image --enabled false --out field.plan.json
26
+ ssobig-writer component plan-set-active --project <slug> --instance timeline --active false --out .ssobig-writer/component-archive.plan.json
27
+ ssobig-writer component plan-set-field --project <slug> --instance clues --field image --enabled false --out .ssobig-writer/field.plan.json
28
28
  ```
29
29
 
30
+ patch·spec·plan·receipt는 기밀 원고를 포함할 수 있으므로 ignored `.ssobig-writer/` 또는 private 경로에 보관하고 커밋하지 않는다.
31
+
30
32
  plan에서 대상, 보존할 data, 생성·제거될 View·binding 또는 `enabledOptionalFields` 변경을 먼저 검토해야 한다. 사용 안 함은 data를 삭제하지 않고 보관하며, 필수·내부·미등록 Component와 필수·미등록 속성 및 임의 구성 변경은 허용하지 않는다.
31
33
 
32
34
  ## 업데이트와 제거
@@ -35,6 +37,7 @@ plan에서 대상, 보존할 data, 생성·제거될 View·binding 또는 `enabl
35
37
  ssobig-writer version --check
36
38
  npm install --global @ssobig/writer-cli@latest
37
39
  ssobig-writer skills status --target <내-workspace>
40
+ # status가 missing이면 install, update-available이면 update, current이면 추가 작업 없음
38
41
  ssobig-writer skills update --target <내-workspace>
39
42
 
40
43
  ssobig-writer auth logout
@@ -51,3 +54,7 @@ CLI를 업데이트한 뒤에는 각 작업공간에서 `skills status`를 확
51
54
  `doctor`는 설치·최신 버전·credential session 존재·Auth health·실행 backend 버전을 읽기 전용으로 진단하고 token, 이메일, 원고 값을 출력하지 않는다. macOS 세션은 Keychain에 저장되고 private Unix daemon을 사용한다. Windows 세션은 현재 Windows 사용자만 해독할 수 있도록 DPAPI CurrentUser로 보호되며 CLI는 background daemon 대신 in-process backend를 자동 사용한다. 따라서 Windows에서는 `agent start`와 `agent stop`이 지원되지 않는다.
52
55
 
53
56
  상세 설치, 업데이트, 삭제 방법은 [SSOBIG WRITER 운영 가이드](https://writer.ssobig.com/)에서 확인한다.
57
+
58
+ ## 작업별 안내
59
+
60
+ 설치된 `ssobig-writer-cli` 스킬은 필요한 참조만 선택한다. 패키지 안에서도 [조회와 검색](tools/writer-cli/skills/ssobig-writer-cli/references/read-search.md), [변경 계획](tools/writer-cli/skills/ssobig-writer-cli/references/projects-components.md), [추리 보드와 spec](tools/writer-cli/skills/ssobig-writer-cli/references/investigation-board.md), [오류 복구](tools/writer-cli/skills/ssobig-writer-cli/references/errors.md)를 읽을 수 있다.
@@ -30,18 +30,23 @@
30
30
  if (typeof options.putLocalAsset !== "function") throw new TypeError("에셋 복원에는 putLocalAsset 함수가 필요합니다.");
31
31
  const queue = buildAssetRestorePlan(manifest);
32
32
  const restoredLocalAssetIds = [];
33
+ const retainedLocalAssetIds = [];
33
34
  const concurrency = Math.max(1, Number(options.concurrency || 6));
34
35
  const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
35
36
  while (queue.length) {
36
37
  const entry = queue.shift();
38
+ if (typeof options.isLocalAssetCurrent === "function" && await options.isLocalAssetCurrent(entry)) {
39
+ retainedLocalAssetIds.push(entry.localAssetId);
40
+ continue;
41
+ }
37
42
  const blob = await options.downloadAsset(entry);
38
43
  if (!blob) continue;
39
- await options.putLocalAsset(entry.localAssetId, blob);
44
+ await options.putLocalAsset(entry.localAssetId, blob, entry);
40
45
  restoredLocalAssetIds.push(entry.localAssetId);
41
46
  }
42
47
  });
43
48
  await Promise.all(workers);
44
- return { clearLocalAssetIds: [], restoredLocalAssetIds };
49
+ return { clearLocalAssetIds: [], restoredLocalAssetIds, retainedLocalAssetIds };
45
50
  }
46
51
 
47
52
  async function defaultContentHash(blob) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssobig/writer-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Official agent CLI for SSOBIG WRITER",
5
5
  "type": "commonjs",
6
6
  "license": "UNLICENSED",
@@ -1,5 +1,5 @@
1
1
  /*! CodeMirror 6 browser bundle - https://codemirror.net/ - MIT License */
2
- var WriterCodeMirror6=(()=>{var zr=Object.defineProperty;var Ju=Object.getOwnPropertyDescriptor;var ed=Object.getOwnPropertyNames;var td=Object.prototype.hasOwnProperty;var id=(n,e)=>{for(var t in e)zr(n,t,{get:e[t],enumerable:!0})},nd=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of ed(e))!td.call(n,r)&&r!==t&&zr(n,r,{get:()=>e[r],enumerable:!(i=Ju(e,r))||i.enumerable});return n};var rd=n=>nd(zr({},"__esModule",{value:!0}),n);var A1={};id(A1,{Decoration:()=>V,EditorState:()=>D,EditorView:()=>A,StateEffect:()=>Y,StateField:()=>he,defaultKeymap:()=>Bc,history:()=>Oc,historyKeymap:()=>Sc,keymap:()=>qt,markdown:()=>Ku});var _r=[],ql=[];(()=>{let n="lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,1n,9,16,o,,x,1i,3,,i,,7,a,2,t,3,1k,,,7,2,2,2,3,9,,a,2,q,,2,3,1k,,,5,4,2,2,3,3,,u,2,3,,b,3,1k,,,8,,3,,3,k,2,m,6,,3,1k,,,7,2,2,2,3,7,3,a,2,u,,1n,5,3,3,,4,9,,14,5,1j,,,7,,3,,4,7,2,b,2,t,3,1k,,,7,,3,,4,7,2,b,2,f,,c,4,1j,2,,7,,3,,4,9,,a,2,t,3,1y,,4,6,,,,8,i,2,1p,,,8,c,8,2q,,,a,b,7,21,2,r,,,,,,4,2,1d,k,,2,5,b,,10,9,,2u,b,,6,n,4,4,3,g,4,d,,,3,6,,f,,jj,3,qa,4,s,3,t,2,u,2,1s,w,9,,19,3,,,39,2,y,,3a,c,4,c,63,5,1l,a,,,,,2,o,2,,1c,1a,2,c,k,5,1b,h,12,9,c,3,u,d,1k,e,1c,k,48,3,,l,4,,6,,2,3,5i,1s,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,n,5,4,,2b,2,1e,i,q,i,d,,12,8,p,d,18,4,1b,e,10,,1v,e,c,,8,2,1a,,1f,,,3,2,2,5,2,,,15,5,5,2,6k,8,,2,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,1t,5,8t,2,25,6,1y,b,1d,4,3e,3,1h,f,15,,2,2,a,4,19,b,7,,1p,3,10,e,g,2,18,,c,3,1c,e,8,4,,2,2k,c,6,,2,,4d,c,l,4,1j,2,,7,2,2,2,3,9,,a,2,2,7,3,5,1v,9,,,2,,,4,,5,,,e,2,2a,i,n,,29,k,6j,7,2,9,r,2,2a,h,2y,d,2t,3,2,a,74,f,6t,6,,2,2,4,,,,2,3x,7,2,7,3,,s,a,14,7,,4,8,,9,b,1a,g,5i,8,5j,8,,8,2a,m,,e,3e,6,3,,,2,,7,,,1u,5,,2,,5,9n,4,9,2,,,1c,7,3,5,n,,44l,,6,f,8ug,i,1xc,5,1n,7,t4,,,1j,7,4,29,,b,2,f57,2,3mp,1a,2,n,f2,5,3,6,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,2s,,4g,7,af,,1p,4,e4,4,72,2,6r,,2,,7,2,5,,d6,7,31,7,240,5".split(",").map(e=>e?parseInt(e,36):1);for(let e=0,t=0;e<n.length;e++)(e%2?ql:_r).push(t=t+n[e])})();function sd(n){if(n<768)return!1;for(let e=0,t=_r.length;;){let i=e+t>>1;if(n<_r[i])t=i;else if(n>=ql[i])e=i+1;else return!0;if(e==t)return!1}}function Wl(n){return n>=127462&&n<=127487}var Dl=8205;function Il(n,e,t=!0,i=!0){return(t?Nl:od)(n,e,i)}function Nl(n,e,t){if(e==n.length)return e;e&&Gl(n.charCodeAt(e))&&Ul(n.charCodeAt(e-1))&&e--;let i=Yr(n,e);for(e+=Bl(i);e<n.length;){let r=Yr(n,e);if(i==Dl||r==Dl||t&&sd(r))e+=Bl(r),i=r;else if(Wl(r)){let s=0,o=e-2;for(;o>=0&&Wl(Yr(n,o));)s++,o-=2;if(s%2==0)break;e+=2}else break}return e}function od(n,e,t){for(;e>1;){let i=Nl(n,e-2,t);if(i<e)return i;e--}return 0}function Yr(n,e){let t=n.charCodeAt(e);if(!Ul(t)||e+1==n.length)return t;let i=n.charCodeAt(e+1);return Gl(i)?(t-55296<<10)+(i-56320)+65536:t}function Gl(n){return n>=56320&&n<57344}function Ul(n){return n>=55296&&n<56320}function Bl(n){return n<65536?1:2}var M=class n{lineAt(e){if(e<0||e>this.length)throw new RangeError(`Invalid position ${e} in document of length ${this.length}`);return this.lineInner(e,!1,1,0)}line(e){if(e<1||e>this.lines)throw new RangeError(`Invalid line number ${e} in ${this.lines}-line document`);return this.lineInner(e,!0,1,0)}replace(e,t,i){[e,t]=si(this,e,t);let r=[];return this.decompose(0,e,r,2),i.length&&i.decompose(0,i.length,r,3),this.decompose(t,this.length,r,1),ii.from(r,this.length-(t-e)+i.length)}append(e){return this.replace(this.length,this.length,e)}slice(e,t=this.length){[e,t]=si(this,e,t);let i=[];return this.decompose(e,t,i,0),ii.from(i,t-e)}eq(e){if(e==this)return!0;if(e.length!=this.length||e.lines!=this.lines)return!1;let t=this.scanIdentical(e,1),i=this.length-this.scanIdentical(e,-1),r=new jt(this),s=new jt(e);for(let o=t,l=t;;){if(r.next(o),s.next(o),o=0,r.lineBreak!=s.lineBreak||r.done!=s.done||r.value!=s.value)return!1;if(l+=r.value.length,r.done||l>=i)return!0}}iter(e=1){return new jt(this,e)}iterRange(e,t=this.length){return new Tn(this,e,t)}iterLines(e,t){let i;if(e==null)i=this.iter();else{t==null&&(t=this.lines+1);let r=this.line(e).from;i=this.iterRange(r,Math.max(r,t==this.lines+1?this.length:t<=1?0:this.line(t-1).to))}return new Cn(i)}toString(){return this.sliceString(0)}toJSON(){let e=[];return this.flatten(e),e}constructor(){}static of(e){if(e.length==0)throw new RangeError("A document must have at least one line");return e.length==1&&!e[0]?n.empty:e.length<=32?new be(e):ii.from(be.split(e,[]))}},be=class n extends M{constructor(e,t=ld(e)){super(),this.text=e,this.length=t}get lines(){return this.text.length}get children(){return null}lineInner(e,t,i,r){for(let s=0;;s++){let o=this.text[s],l=r+o.length;if((t?i:l)>=e)return new Wr(r,l,i,o);r=l+1,i++}}decompose(e,t,i,r){let s=e<=0&&t>=this.length?this:new n(Fl(this.text,e,t),Math.min(t,this.length)-Math.max(0,e));if(r&1){let o=i.pop(),l=vn(s.text,o.text.slice(),0,s.length);if(l.length<=32)i.push(new n(l,o.length+s.length));else{let a=l.length>>1;i.push(new n(l.slice(0,a)),new n(l.slice(a)))}}else i.push(s)}replace(e,t,i){if(!(i instanceof n))return super.replace(e,t,i);[e,t]=si(this,e,t);let r=vn(this.text,vn(i.text,Fl(this.text,0,e)),t),s=this.length+i.length-(t-e);return r.length<=32?new n(r,s):ii.from(n.split(r,[]),s)}sliceString(e,t=this.length,i=`
2
+ var WriterCodeMirror6=(()=>{var zr=Object.defineProperty;var Ju=Object.getOwnPropertyDescriptor;var ed=Object.getOwnPropertyNames;var td=Object.prototype.hasOwnProperty;var id=(n,e)=>{for(var t in e)zr(n,t,{get:e[t],enumerable:!0})},nd=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of ed(e))!td.call(n,r)&&r!==t&&zr(n,r,{get:()=>e[r],enumerable:!(i=Ju(e,r))||i.enumerable});return n};var rd=n=>nd(zr({},"__esModule",{value:!0}),n);var A1={};id(A1,{Decoration:()=>V,EditorState:()=>D,EditorView:()=>A,StateEffect:()=>Y,StateField:()=>he,defaultKeymap:()=>Bc,deleteMarkupBackward:()=>$1,history:()=>Oc,historyKeymap:()=>Sc,insertNewlineContinueMarkupCommand:()=>Q1,keymap:()=>qt,markdown:()=>Ku});var _r=[],ql=[];(()=>{let n="lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,1n,9,16,o,,x,1i,3,,i,,7,a,2,t,3,1k,,,7,2,2,2,3,9,,a,2,q,,2,3,1k,,,5,4,2,2,3,3,,u,2,3,,b,3,1k,,,8,,3,,3,k,2,m,6,,3,1k,,,7,2,2,2,3,7,3,a,2,u,,1n,5,3,3,,4,9,,14,5,1j,,,7,,3,,4,7,2,b,2,t,3,1k,,,7,,3,,4,7,2,b,2,f,,c,4,1j,2,,7,,3,,4,9,,a,2,t,3,1y,,4,6,,,,8,i,2,1p,,,8,c,8,2q,,,a,b,7,21,2,r,,,,,,4,2,1d,k,,2,5,b,,10,9,,2u,b,,6,n,4,4,3,g,4,d,,,3,6,,f,,jj,3,qa,4,s,3,t,2,u,2,1s,w,9,,19,3,,,39,2,y,,3a,c,4,c,63,5,1l,a,,,,,2,o,2,,1c,1a,2,c,k,5,1b,h,12,9,c,3,u,d,1k,e,1c,k,48,3,,l,4,,6,,2,3,5i,1s,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,n,5,4,,2b,2,1e,i,q,i,d,,12,8,p,d,18,4,1b,e,10,,1v,e,c,,8,2,1a,,1f,,,3,2,2,5,2,,,15,5,5,2,6k,8,,2,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,1t,5,8t,2,25,6,1y,b,1d,4,3e,3,1h,f,15,,2,2,a,4,19,b,7,,1p,3,10,e,g,2,18,,c,3,1c,e,8,4,,2,2k,c,6,,2,,4d,c,l,4,1j,2,,7,2,2,2,3,9,,a,2,2,7,3,5,1v,9,,,2,,,4,,5,,,e,2,2a,i,n,,29,k,6j,7,2,9,r,2,2a,h,2y,d,2t,3,2,a,74,f,6t,6,,2,2,4,,,,2,3x,7,2,7,3,,s,a,14,7,,4,8,,9,b,1a,g,5i,8,5j,8,,8,2a,m,,e,3e,6,3,,,2,,7,,,1u,5,,2,,5,9n,4,9,2,,,1c,7,3,5,n,,44l,,6,f,8ug,i,1xc,5,1n,7,t4,,,1j,7,4,29,,b,2,f57,2,3mp,1a,2,n,f2,5,3,6,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,2s,,4g,7,af,,1p,4,e4,4,72,2,6r,,2,,7,2,5,,d6,7,31,7,240,5".split(",").map(e=>e?parseInt(e,36):1);for(let e=0,t=0;e<n.length;e++)(e%2?ql:_r).push(t=t+n[e])})();function sd(n){if(n<768)return!1;for(let e=0,t=_r.length;;){let i=e+t>>1;if(n<_r[i])t=i;else if(n>=ql[i])e=i+1;else return!0;if(e==t)return!1}}function Wl(n){return n>=127462&&n<=127487}var Dl=8205;function Il(n,e,t=!0,i=!0){return(t?Nl:od)(n,e,i)}function Nl(n,e,t){if(e==n.length)return e;e&&Gl(n.charCodeAt(e))&&Ul(n.charCodeAt(e-1))&&e--;let i=Yr(n,e);for(e+=Bl(i);e<n.length;){let r=Yr(n,e);if(i==Dl||r==Dl||t&&sd(r))e+=Bl(r),i=r;else if(Wl(r)){let s=0,o=e-2;for(;o>=0&&Wl(Yr(n,o));)s++,o-=2;if(s%2==0)break;e+=2}else break}return e}function od(n,e,t){for(;e>1;){let i=Nl(n,e-2,t);if(i<e)return i;e--}return 0}function Yr(n,e){let t=n.charCodeAt(e);if(!Ul(t)||e+1==n.length)return t;let i=n.charCodeAt(e+1);return Gl(i)?(t-55296<<10)+(i-56320)+65536:t}function Gl(n){return n>=56320&&n<57344}function Ul(n){return n>=55296&&n<56320}function Bl(n){return n<65536?1:2}var M=class n{lineAt(e){if(e<0||e>this.length)throw new RangeError(`Invalid position ${e} in document of length ${this.length}`);return this.lineInner(e,!1,1,0)}line(e){if(e<1||e>this.lines)throw new RangeError(`Invalid line number ${e} in ${this.lines}-line document`);return this.lineInner(e,!0,1,0)}replace(e,t,i){[e,t]=si(this,e,t);let r=[];return this.decompose(0,e,r,2),i.length&&i.decompose(0,i.length,r,3),this.decompose(t,this.length,r,1),ii.from(r,this.length-(t-e)+i.length)}append(e){return this.replace(this.length,this.length,e)}slice(e,t=this.length){[e,t]=si(this,e,t);let i=[];return this.decompose(e,t,i,0),ii.from(i,t-e)}eq(e){if(e==this)return!0;if(e.length!=this.length||e.lines!=this.lines)return!1;let t=this.scanIdentical(e,1),i=this.length-this.scanIdentical(e,-1),r=new jt(this),s=new jt(e);for(let o=t,l=t;;){if(r.next(o),s.next(o),o=0,r.lineBreak!=s.lineBreak||r.done!=s.done||r.value!=s.value)return!1;if(l+=r.value.length,r.done||l>=i)return!0}}iter(e=1){return new jt(this,e)}iterRange(e,t=this.length){return new Tn(this,e,t)}iterLines(e,t){let i;if(e==null)i=this.iter();else{t==null&&(t=this.lines+1);let r=this.line(e).from;i=this.iterRange(r,Math.max(r,t==this.lines+1?this.length:t<=1?0:this.line(t-1).to))}return new Cn(i)}toString(){return this.sliceString(0)}toJSON(){let e=[];return this.flatten(e),e}constructor(){}static of(e){if(e.length==0)throw new RangeError("A document must have at least one line");return e.length==1&&!e[0]?n.empty:e.length<=32?new be(e):ii.from(be.split(e,[]))}},be=class n extends M{constructor(e,t=ld(e)){super(),this.text=e,this.length=t}get lines(){return this.text.length}get children(){return null}lineInner(e,t,i,r){for(let s=0;;s++){let o=this.text[s],l=r+o.length;if((t?i:l)>=e)return new Wr(r,l,i,o);r=l+1,i++}}decompose(e,t,i,r){let s=e<=0&&t>=this.length?this:new n(Fl(this.text,e,t),Math.min(t,this.length)-Math.max(0,e));if(r&1){let o=i.pop(),l=vn(s.text,o.text.slice(),0,s.length);if(l.length<=32)i.push(new n(l,o.length+s.length));else{let a=l.length>>1;i.push(new n(l.slice(0,a)),new n(l.slice(a)))}}else i.push(s)}replace(e,t,i){if(!(i instanceof n))return super.replace(e,t,i);[e,t]=si(this,e,t);let r=vn(this.text,vn(i.text,Fl(this.text,0,e)),t),s=this.length+i.length-(t-e);return r.length<=32?new n(r,s):ii.from(n.split(r,[]),s)}sliceString(e,t=this.length,i=`
3
3
  `){[e,t]=si(this,e,t);let r="";for(let s=0,o=0;s<=t&&o<this.text.length;o++){let l=this.text[o],a=s+l.length;s>e&&o&&(r+=i),e<a&&t>s&&(r+=l.slice(Math.max(0,e-s),t-s)),s=a+1}return r}flatten(e){for(let t of this.text)e.push(t)}scanIdentical(){return 0}static split(e,t){let i=[],r=-1;for(let s of e)i.push(s),r+=s.length+1,i.length==32&&(t.push(new n(i,r)),i=[],r=-1);return r>-1&&t.push(new n(i,r)),t}},ii=class n extends M{constructor(e,t){super(),this.children=e,this.length=t,this.lines=0;for(let i of e)this.lines+=i.lines}lineInner(e,t,i,r){for(let s=0;;s++){let o=this.children[s],l=r+o.length,a=i+o.lines-1;if((t?a:l)>=e)return o.lineInner(e,t,i,r);r=l+1,i=a+1}}decompose(e,t,i,r){for(let s=0,o=0;o<=t&&s<this.children.length;s++){let l=this.children[s],a=o+l.length;if(e<=a&&t>=o){let h=r&((o<=e?1:0)|(a>=t?2:0));o>=e&&a<=t&&!h?i.push(l):l.decompose(e-o,t-o,i,h)}o=a+1}}replace(e,t,i){if([e,t]=si(this,e,t),i.lines<this.lines)for(let r=0,s=0;r<this.children.length;r++){let o=this.children[r],l=s+o.length;if(e>=s&&t<=l){let a=o.replace(e-s,t-s,i),h=this.lines-o.lines+a.lines;if(a.lines<h>>4&&a.lines>h>>6){let c=this.children.slice();return c[r]=a,new n(c,this.length-(t-e)+i.length)}return super.replace(s,l,a)}s=l+1}return super.replace(e,t,i)}sliceString(e,t=this.length,i=`
4
4
  `){[e,t]=si(this,e,t);let r="";for(let s=0,o=0;s<this.children.length&&o<=t;s++){let l=this.children[s],a=o+l.length;o>e&&s&&(r+=i),e<a&&t>o&&(r+=l.sliceString(e-o,t-o,i)),o=a+1}return r}flatten(e){for(let t of this.children)t.flatten(e)}scanIdentical(e,t){if(!(e instanceof n))return 0;let i=0,[r,s,o,l]=t>0?[0,0,this.children.length,e.children.length]:[this.children.length-1,e.children.length-1,-1,-1];for(;;r+=t,s+=t){if(r==o||s==l)return i;let a=this.children[r],h=e.children[s];if(a!=h)return i+a.scanIdentical(h,t);i+=a.length+1}}static from(e,t=e.reduce((i,r)=>i+r.length+1,-1)){let i=0;for(let d of e)i+=d.lines;if(i<32){let d=[];for(let O of e)O.flatten(d);return new be(d,t)}let r=Math.max(32,i>>5),s=r<<1,o=r>>1,l=[],a=0,h=-1,c=[];function f(d){let O;if(d.lines>s&&d instanceof n)for(let m of d.children)f(m);else d.lines>o&&(a>o||!a)?(u(),l.push(d)):d instanceof be&&a&&(O=c[c.length-1])instanceof be&&d.lines+O.lines<=32?(a+=d.lines,h+=d.length+1,c[c.length-1]=new be(O.text.concat(d.text),O.length+1+d.length)):(a+d.lines>r&&u(),a+=d.lines,h+=d.length+1,c.push(d))}function u(){a!=0&&(l.push(c.length==1?c[0]:n.from(c,h)),h=-1,a=c.length=0)}for(let d of e)f(d);return u(),l.length==1?l[0]:new n(l,t)}};M.empty=new be([""],0);function ld(n){let e=-1;for(let t of n)e+=t.length+1;return e}function vn(n,e,t=0,i=1e9){for(let r=0,s=0,o=!0;s<n.length&&r<=i;s++){let l=n[s],a=r+l.length;a>=t&&(a>i&&(l=l.slice(0,i-r)),r<t&&(l=l.slice(t-r)),o?(e[e.length-1]+=l,o=!1):e.push(l)),r=a+1}return e}function Fl(n,e,t){return vn(n,[""],e,t)}var jt=class{constructor(e,t=1){this.dir=t,this.done=!1,this.lineBreak=!1,this.value="",this.nodes=[e],this.offsets=[t>0?1:(e instanceof be?e.text.length:e.children.length)<<1]}nextInner(e,t){for(this.done=this.lineBreak=!1;;){let i=this.nodes.length-1,r=this.nodes[i],s=this.offsets[i],o=s>>1,l=r instanceof be?r.text.length:r.children.length;if(o==(t>0?l:0)){if(i==0)return this.done=!0,this.value="",this;t>0&&this.offsets[i-1]++,this.nodes.pop(),this.offsets.pop()}else if((s&1)==(t>0?0:1)){if(this.offsets[i]+=t,e==0)return this.lineBreak=!0,this.value=`
5
5
  `,this;e--}else if(r instanceof be){let a=r.text[o+(t<0?-1:0)];if(this.offsets[i]+=t,a.length>Math.max(0,e))return this.value=e==0?a:t>0?a.slice(e):a.slice(0,a.length-e),this;e-=a.length}else{let a=r.children[o+(t<0?-1:0)];e>a.length?(e-=a.length,this.offsets[i]+=t):(t<0&&this.offsets[i]--,this.nodes.push(a),this.offsets.push(t>0?1:(a instanceof be?a.text.length:a.children.length)<<1))}}}next(e=0){return e<0&&(this.nextInner(-e,-this.dir),e=this.value.length),this.nextInner(e,this.dir)}},Tn=class{constructor(e,t,i){this.value="",this.done=!1,this.cursor=new jt(e,t>i?-1:1),this.pos=t>i?e.length:0,this.from=Math.min(t,i),this.to=Math.max(t,i)}nextInner(e,t){if(t<0?this.pos<=this.from:this.pos>=this.to)return this.value="",this.done=!0,this;e+=Math.max(0,t<0?this.pos-this.to:this.from-this.pos);let i=t<0?this.pos-this.from:this.to-this.pos;e>i&&(e=i),i-=e;let{value:r}=this.cursor.next(e);return this.pos+=(r.length+e)*t,this.value=r.length<=i?r:t<0?r.slice(r.length-i):r.slice(0,i),this.done=!this.value,this}next(e=0){return e<0?e=Math.max(e,this.from-this.pos):e>0&&(e=Math.min(e,this.to-this.pos)),this.nextInner(e,this.cursor.dir)}get lineBreak(){return this.cursor.lineBreak&&this.value!=""}},Cn=class{constructor(e){this.inner=e,this.afterBreak=!0,this.value="",this.done=!1}next(e=0){let{done:t,lineBreak:i,value:r}=this.inner.next(e);return t&&this.afterBreak?(this.value="",this.afterBreak=!1):t?(this.done=!0,this.value=""):i?this.afterBreak?this.value="":(this.afterBreak=!0,this.next()):(this.value=r,this.afterBreak=!1),this}get lineBreak(){return!1}};typeof Symbol<"u"&&(M.prototype[Symbol.iterator]=function(){return this.iter()},jt.prototype[Symbol.iterator]=Tn.prototype[Symbol.iterator]=Cn.prototype[Symbol.iterator]=function(){return this});var Wr=class{constructor(e,t,i,r){this.from=e,this.to=t,this.number=i,this.text=r}get length(){return this.to-this.from}};function si(n,e,t){return e=Math.max(0,Math.min(n.length,e)),[e,Math.max(e,Math.min(n.length,t))]}function ie(n,e,t=!0,i=!0){return Il(n,e,t,i)}function ad(n){return n>=56320&&n<57344}function hd(n){return n>=55296&&n<56320}function ts(n,e){let t=n.charCodeAt(e);if(!hd(t)||e+1==n.length)return t;let i=n.charCodeAt(e+1);return ad(i)?(t-55296<<10)+(i-56320)+65536:t}function is(n){return n<65536?1:2}var Dr=/\r\n?|\n/,se=(function(n){return n[n.Simple=0]="Simple",n[n.TrackDel=1]="TrackDel",n[n.TrackBefore=2]="TrackBefore",n[n.TrackAfter=3]="TrackAfter",n})(se||(se={})),Je=class n{constructor(e){this.sections=e}get length(){let e=0;for(let t=0;t<this.sections.length;t+=2)e+=this.sections[t];return e}get newLength(){let e=0;for(let t=0;t<this.sections.length;t+=2){let i=this.sections[t+1];e+=i<0?this.sections[t]:i}return e}get empty(){return this.sections.length==0||this.sections.length==2&&this.sections[1]<0}iterGaps(e){for(let t=0,i=0,r=0;t<this.sections.length;){let s=this.sections[t++],o=this.sections[t++];o<0?(e(i,r,s),r+=s):r+=o,i+=s}}iterChangedRanges(e,t=!1){Br(this,e,t)}get invertedDesc(){let e=[];for(let t=0;t<this.sections.length;){let i=this.sections[t++],r=this.sections[t++];r<0?e.push(i,r):e.push(r,i)}return new n(e)}composeDesc(e){return this.empty?e:e.empty?this:ta(this,e)}mapDesc(e,t=!1){return e.empty?this:qr(this,e,t)}mapPos(e,t=-1,i=se.Simple){let r=0,s=0;for(let o=0;o<this.sections.length;){let l=this.sections[o++],a=this.sections[o++],h=r+l;if(a<0){if(h>e)return s+(e-r);s+=l}else{if(i!=se.Simple&&h>=e&&(i==se.TrackDel&&r<e&&h>e||i==se.TrackBefore&&r<e||i==se.TrackAfter&&h>e))return null;if(h>e||h==e&&t<0&&!l)return e==r||t<0?s:s+a;s+=a}r=h}if(e>r)throw new RangeError(`Position ${e} is out of range for changeset of length ${r}`);return s}touchesRange(e,t=e){for(let i=0,r=0;i<this.sections.length&&r<=t;){let s=this.sections[i++],o=this.sections[i++],l=r+s;if(o>=0&&r<=t&&l>=e)return r<e&&l>t?"cover":!0;r=l}return!1}toString(){let e="";for(let t=0;t<this.sections.length;){let i=this.sections[t++],r=this.sections[t++];e+=(e?" ":"")+i+(r>=0?":"+r:"")}return e}toJSON(){return this.sections}static fromJSON(e){if(!Array.isArray(e)||e.length%2||e.some(t=>typeof t!="number"))throw new RangeError("Invalid JSON representation of ChangeDesc");return new n(e)}static create(e){return new n(e)}},ae=class n extends Je{constructor(e,t){super(e),this.inserted=t}apply(e){if(this.length!=e.length)throw new RangeError("Applying change set to a document with the wrong length");return Br(this,(t,i,r,s,o)=>e=e.replace(r,r+(i-t),o),!1),e}mapDesc(e,t=!1){return qr(this,e,t,!0)}invert(e){let t=this.sections.slice(),i=[];for(let r=0,s=0;r<t.length;r+=2){let o=t[r],l=t[r+1];if(l>=0){t[r]=l,t[r+1]=o;let a=r>>1;for(;i.length<a;)i.push(M.empty);i.push(o?e.slice(s,s+o):M.empty)}s+=o}return new n(t,i)}compose(e){return this.empty?e:e.empty?this:ta(this,e,!0)}map(e,t=!1){return e.empty?this:qr(this,e,t,!0)}iterChanges(e,t=!1){Br(this,e,t)}get desc(){return Je.create(this.sections)}filter(e){let t=[],i=[],r=[],s=new zt(this);e:for(let o=0,l=0;;){let a=o==e.length?1e9:e[o++];for(;l<a||l==a&&s.len==0;){if(s.done)break e;let c=Math.min(s.len,a-l);oe(r,c,-1);let f=s.ins==-1?-1:s.off==0?s.ins:0;oe(t,c,f),f>0&&dt(i,t,s.text),s.forward(c),l+=c}let h=e[o++];for(;l<h;){if(s.done)break e;let c=Math.min(s.len,h-l);oe(t,c,-1),oe(r,c,s.ins==-1?-1:s.off==0?s.ins:0),s.forward(c),l+=c}}return{changes:new n(t,i),filtered:Je.create(r)}}toJSON(){let e=[];for(let t=0;t<this.sections.length;t+=2){let i=this.sections[t],r=this.sections[t+1];r<0?e.push(i):r==0?e.push([i]):e.push([i].concat(this.inserted[t>>1].toJSON()))}return e}static of(e,t,i){let r=[],s=[],o=0,l=null;function a(c=!1){if(!c&&!r.length)return;o<t&&oe(r,t-o,-1);let f=new n(r,s);l=l?l.compose(f.map(l)):f,r=[],s=[],o=0}function h(c){if(Array.isArray(c))for(let f of c)h(f);else if(c instanceof n){if(c.length!=t)throw new RangeError(`Mismatched change set length (got ${c.length}, expected ${t})`);a(),l=l?l.compose(c.map(l)):c}else{let{from:f,to:u=f,insert:d}=c;if(f>u||f<0||u>t)throw new RangeError(`Invalid change range ${f} to ${u} (in doc of length ${t})`);let O=d?typeof d=="string"?M.of(d.split(i||Dr)):d:M.empty,m=O.length;if(f==u&&m==0)return;f<o&&a(),f>o&&oe(r,f-o,-1),oe(r,u-f,m),dt(s,r,O),o=u}}return h(e),a(!l),l}static empty(e){return new n(e?[e,-1]:[],[])}static fromJSON(e){if(!Array.isArray(e))throw new RangeError("Invalid JSON representation of ChangeSet");let t=[],i=[];for(let r=0;r<e.length;r++){let s=e[r];if(typeof s=="number")t.push(s,-1);else{if(!Array.isArray(s)||typeof s[0]!="number"||s.some((o,l)=>l&&typeof o!="string"))throw new RangeError("Invalid JSON representation of ChangeSet");if(s.length==1)t.push(s[0],0);else{for(;i.length<r;)i.push(M.empty);i[r]=M.of(s.slice(1)),t.push(s[0],i[r].length)}}}return new n(t,i)}static createSet(e,t){return new n(e,t)}};function oe(n,e,t,i=!1){if(e==0&&t<=0)return;let r=n.length-2;r>=0&&t<=0&&t==n[r+1]?n[r]+=e:r>=0&&e==0&&n[r]==0?n[r+1]+=t:i?(n[r]+=e,n[r+1]+=t):n.push(e,t)}function dt(n,e,t){if(t.length==0)return;let i=e.length-2>>1;if(i<n.length)n[n.length-1]=n[n.length-1].append(t);else{for(;n.length<i;)n.push(M.empty);n.push(t)}}function Br(n,e,t){let i=n.inserted;for(let r=0,s=0,o=0;o<n.sections.length;){let l=n.sections[o++],a=n.sections[o++];if(a<0)r+=l,s+=l;else{let h=r,c=s,f=M.empty;for(;h+=l,c+=a,a&&i&&(f=f.append(i[o-2>>1])),!(t||o==n.sections.length||n.sections[o+1]<0);)l=n.sections[o++],a=n.sections[o++];e(r,h,s,c,f),r=h,s=c}}}function qr(n,e,t,i=!1){let r=[],s=i?[]:null,o=new zt(n),l=new zt(e);for(let a=-1;;){if(o.done&&l.len||l.done&&o.len)throw new Error("Mismatched change set lengths");if(o.ins==-1&&l.ins==-1){let h=Math.min(o.len,l.len);oe(r,h,-1),o.forward(h),l.forward(h)}else if(l.ins>=0&&(o.ins<0||a==o.i||o.off==0&&(l.len<o.len||l.len==o.len&&!t))){let h=l.len;for(oe(r,l.ins,-1);h;){let c=Math.min(o.len,h);o.ins>=0&&a<o.i&&o.len<=c&&(oe(r,0,o.ins),s&&dt(s,r,o.text),a=o.i),o.forward(c),h-=c}l.next()}else if(o.ins>=0){let h=0,c=o.len;for(;c;)if(l.ins==-1){let f=Math.min(c,l.len);h+=f,c-=f,l.forward(f)}else if(l.ins==0&&l.len<c)c-=l.len,l.next();else break;oe(r,h,a<o.i?o.ins:0),s&&a<o.i&&dt(s,r,o.text),a=o.i,o.forward(o.len-c)}else{if(o.done&&l.done)return s?ae.createSet(r,s):Je.create(r);throw new Error("Mismatched change set lengths")}}}function ta(n,e,t=!1){let i=[],r=t?[]:null,s=new zt(n),o=new zt(e);for(let l=!1;;){if(s.done&&o.done)return r?ae.createSet(i,r):Je.create(i);if(s.ins==0)oe(i,s.len,0,l),s.next();else if(o.len==0&&!o.done)oe(i,0,o.ins,l),r&&dt(r,i,o.text),o.next();else{if(s.done||o.done)throw new Error("Mismatched change set lengths");{let a=Math.min(s.len2,o.len),h=i.length;if(s.ins==-1){let c=o.ins==-1?-1:o.off?0:o.ins;oe(i,a,c,l),r&&c&&dt(r,i,o.text)}else o.ins==-1?(oe(i,s.off?0:s.len,a,l),r&&dt(r,i,s.textBit(a))):(oe(i,s.off?0:s.len,o.off?0:o.ins,l),r&&!o.off&&dt(r,i,o.text));l=(s.ins>a||o.ins>=0&&o.len>a)&&(l||i.length>h),s.forward2(a),o.forward(a)}}}}var zt=class{constructor(e){this.set=e,this.i=0,this.next()}next(){let{sections:e}=this.set;this.i<e.length?(this.len=e[this.i++],this.ins=e[this.i++]):(this.len=0,this.ins=-2),this.off=0}get done(){return this.ins==-2}get len2(){return this.ins<0?this.len:this.ins}get text(){let{inserted:e}=this.set,t=this.i-2>>1;return t>=e.length?M.empty:e[t]}textBit(e){let{inserted:t}=this.set,i=this.i-2>>1;return i>=t.length&&!e?M.empty:t[i].slice(this.off,e==null?void 0:this.off+e)}forward(e){e==this.len?this.next():(this.len-=e,this.off+=e)}forward2(e){this.ins==-1?this.forward(e):e==this.ins?this.next():(this.ins-=e,this.off+=e)}},Mt=class n{constructor(e,t,i,r){this.from=e,this.to=t,this.flags=i,this.goalColumn=r}get anchor(){return this.flags&32?this.to:this.from}get head(){return this.flags&32?this.from:this.to}get empty(){return this.from==this.to}get assoc(){return this.flags&8?-1:this.flags&16?1:0}get undirectional(){return(this.flags&64)>0}get bidiLevel(){let e=this.flags&7;return e==7?null:e}map(e,t=-1){let i,r;return this.empty?i=r=e.mapPos(this.from,t):(i=e.mapPos(this.from,1),r=e.mapPos(this.to,-1)),i==this.from&&r==this.to?this:new n(i,r,this.flags,this.goalColumn)}extend(e,t=e,i=0){if(e<=this.anchor&&t>=this.anchor)return b.range(e,t,void 0,void 0,i);let r=Math.abs(e-this.anchor)>Math.abs(t-this.anchor)?e:t;return b.range(this.anchor,r,void 0,void 0,i)}eq(e,t=!1){return this.anchor==e.anchor&&this.head==e.head&&this.goalColumn==e.goalColumn&&(!t||!this.empty||this.assoc==e.assoc)}toJSON(){return{anchor:this.anchor,head:this.head}}static fromJSON(e){if(!e||typeof e.anchor!="number"||typeof e.head!="number")throw new RangeError("Invalid JSON representation for SelectionRange");return b.range(e.anchor,e.head)}static create(e,t,i,r){return new n(e,t,i,r)}},b=class n{constructor(e,t){this.ranges=e,this.mainIndex=t}map(e,t=-1){return e.empty?this:n.create(this.ranges.map(i=>i.map(e,t)),this.mainIndex)}eq(e,t=!1){if(this.ranges.length!=e.ranges.length||this.mainIndex!=e.mainIndex)return!1;for(let i=0;i<this.ranges.length;i++)if(!this.ranges[i].eq(e.ranges[i],t))return!1;return!0}get main(){return this.ranges[this.mainIndex]}asSingle(){return this.ranges.length==1?this:new n([this.main],0)}addRange(e,t=!0){return n.create([e].concat(this.ranges),t?0:this.mainIndex+1)}replaceRange(e,t=this.mainIndex){let i=this.ranges.slice();return i[t]=e,n.create(i,this.mainIndex)}toJSON(){return{ranges:this.ranges.map(e=>e.toJSON()),main:this.mainIndex}}static fromJSON(e){if(!e||!Array.isArray(e.ranges)||typeof e.main!="number"||e.main>=e.ranges.length)throw new RangeError("Invalid JSON representation for EditorSelection");return new n(e.ranges.map(t=>Mt.fromJSON(t)),e.main)}static single(e,t=e){return new n([n.range(e,t)],0)}static create(e,t=0){if(e.length==0)throw new RangeError("A selection needs at least one range");for(let i=0,r=0;r<e.length;r++){let s=e[r];if(s.empty?s.from<=i:s.from<i)return n.normalized(e.slice(),t);i=s.to}return new n(e,t)}static cursor(e,t=0,i,r){return Mt.create(e,e,(t==0?0:t<0?8:16)|(i==null?7:Math.min(6,i)),r)}static range(e,t,i,r,s){let o=r==null?7:Math.min(6,r);return!s&&e!=t&&(s=t<e?1:-1),s&&(o|=s<0?8:16),t<e?Mt.create(t,e,o|32,i):Mt.create(e,t,o,i)}static undirectionalRange(e,t){return Mt.create(e,t,64,void 0)}static normalized(e,t=0){let i=e[t];e.sort((r,s)=>r.from-s.from),t=e.indexOf(i);for(let r=1;r<e.length;r++){let s=e[r],o=e[r-1];if(s.empty?s.from<=o.to:s.from<o.to){let l=o.from,a=Math.max(s.to,o.to);r<=t&&t--,e.splice(--r,2,s.anchor>s.head?n.range(a,l):n.range(l,a))}}return new n(e,t)}};function ia(n,e){for(let t of n.ranges)if(t.to>e)throw new RangeError("Selection points outside of document")}var ns=0,C=class n{constructor(e,t,i,r,s){this.combine=e,this.compareInput=t,this.compare=i,this.isStatic=r,this.id=ns++,this.default=e([]),this.extensions=typeof s=="function"?s(this):s}get reader(){return this}static define(e={}){return new n(e.combine||(t=>t),e.compareInput||((t,i)=>t===i),e.compare||(e.combine?(t,i)=>t===i:rs),!!e.static,e.enables)}of(e){return new ni([],this,0,e)}compute(e,t){if(this.isStatic)throw new Error("Can't compute a static facet");return new ni(e,this,1,t)}computeN(e,t){if(this.isStatic)throw new Error("Can't compute a static facet");return new ni(e,this,2,t)}from(e,t){return t||(t=i=>i),this.compute([e],i=>t(i.field(e)))}};function rs(n,e){return n==e||n.length==e.length&&n.every((t,i)=>t===e[i])}var ni=class{constructor(e,t,i,r){this.dependencies=e,this.facet=t,this.type=i,this.value=r,this.id=ns++}dynamicSlot(e){var t;let i=this.value,r=this.facet.compareInput,s=this.id,o=e[s]>>1,l=this.type==2,a=!1,h=!1,c=[];for(let f of this.dependencies)f=="doc"?a=!0:f=="selection"?h=!0:(((t=e[f.id])!==null&&t!==void 0?t:1)&1)==0&&c.push(e[f.id]);return{create(f){return f.values[o]=i(f),1},update(f,u){if(a&&u.docChanged||h&&(u.docChanged||u.selection)||Ir(f,c)){let d=i(f);if(l?!Hl(d,f.values[o],r):!r(d,f.values[o]))return f.values[o]=d,1}return 0},reconfigure:(f,u)=>{let d,O=u.config.address[s];if(O!=null){let m=Rn(u,O);if(this.dependencies.every(g=>g instanceof C?u.facet(g)===f.facet(g):g instanceof he?u.field(g,!1)==f.field(g,!1):!0)||(l?Hl(d=i(f),m,r):r(d=i(f),m)))return f.values[o]=m,0}else d=i(f);return f.values[o]=d,1}}}get extension(){return this}};function Hl(n,e,t){if(n.length!=e.length)return!1;for(let i=0;i<n.length;i++)if(!t(n[i],e[i]))return!1;return!0}function Ir(n,e){let t=!1;for(let i of e)Ai(n,i)&1&&(t=!0);return t}function cd(n,e,t){let i=t.map(a=>n[a.id]),r=t.map(a=>a.type),s=i.filter(a=>!(a&1)),o=n[e.id]>>1;function l(a){let h=[];for(let c=0;c<i.length;c++){let f=Rn(a,i[c]);if(r[c]==2)for(let u of f)h.push(u);else h.push(f)}return e.combine(h)}return{create(a){for(let h of i)Ai(a,h);return a.values[o]=l(a),1},update(a,h){if(!Ir(a,s))return 0;let c=l(a);return e.compare(c,a.values[o])?0:(a.values[o]=c,1)},reconfigure(a,h){let c=Ir(a,i),f=h.config.facets[e.id],u=h.facet(e);if(f&&!c&&rs(t,f))return a.values[o]=u,0;let d=l(a);return e.compare(d,u)?(a.values[o]=u,0):(a.values[o]=d,1)}}}var wn=C.define({static:!0}),he=class n{constructor(e,t,i,r,s){this.id=e,this.createF=t,this.updateF=i,this.compareF=r,this.spec=s,this.provides=void 0}static define(e){let t=new n(ns++,e.create,e.update,e.compare||((i,r)=>i===r),e);return e.provide&&(t.provides=e.provide(t)),t}create(e){let t=e.facet(wn).find(i=>i.field==this);return(t?.create||this.createF)(e)}slot(e){let t=e[this.id]>>1;return{create:i=>(i.values[t]=this.create(i),1),update:(i,r)=>{let s=i.values[t],o=this.updateF(s,r);return this.compareF(s,o)?0:(i.values[t]=o,1)},reconfigure:(i,r)=>{let s=i.facet(wn),o=r.facet(wn),l;return(l=s.find(a=>a.field==this))&&l!=o.find(a=>a.field==this)?(i.values[t]=l.create(i),1):r.config.address[this.id]!=null?(i.values[t]=r.field(this),0):(i.values[t]=this.create(i),1)}}}init(e){return[this,wn.of({field:this,create:e})]}get extension(){return this}},Lt={lowest:4,low:3,default:2,high:1,highest:0};function Zi(n){return e=>new Zn(e,n)}var et={highest:Zi(Lt.highest),high:Zi(Lt.high),default:Zi(Lt.default),low:Zi(Lt.low),lowest:Zi(Lt.lowest)},Zn=class{constructor(e,t){this.inner=e,this.prec=t}get extension(){return this}},An=class n{of(e){return new Xi(this,e)}reconfigure(e){return n.reconfigure.of({compartment:this,extension:e})}get(e){return e.config.compartments.get(this)}},Xi=class{constructor(e,t){this.compartment=e,this.inner=t}get extension(){return this}},Xn=class n{constructor(e,t,i,r,s,o){for(this.base=e,this.compartments=t,this.dynamicSlots=i,this.address=r,this.staticValues=s,this.facets=o,this.statusTemplate=[];this.statusTemplate.length<i.length;)this.statusTemplate.push(0)}staticFacet(e){let t=this.address[e.id];return t==null?e.default:this.staticValues[t>>1]}static resolve(e,t,i){let r=[],s=Object.create(null),o=new Map;for(let u of fd(e,t,o))u instanceof he?r.push(u):(s[u.facet.id]||(s[u.facet.id]=[])).push(u);let l=Object.create(null),a=[],h=[];for(let u of r)l[u.id]=h.length<<1,h.push(d=>u.slot(d));let c=i?.config.facets;for(let u in s){let d=s[u],O=d[0].facet,m=c&&c[u]||[];if(d.every(g=>g.type==0))if(l[O.id]=a.length<<1|1,rs(m,d))a.push(i.facet(O));else{let g=O.combine(d.map(S=>S.value));a.push(i&&O.compare(g,i.facet(O))?i.facet(O):g)}else{for(let g of d)g.type==0?(l[g.id]=a.length<<1|1,a.push(g.value)):(l[g.id]=h.length<<1,h.push(S=>g.dynamicSlot(S)));l[O.id]=h.length<<1,h.push(g=>cd(g,O,d))}}let f=h.map(u=>u(l));return new n(e,o,f,l,a,s)}};function fd(n,e,t){let i=[[],[],[],[],[]],r=new Map;function s(o,l){let a=r.get(o);if(a!=null){if(a<=l)return;let h=i[a].indexOf(o);h>-1&&i[a].splice(h,1),o instanceof Xi&&t.delete(o.compartment)}if(r.set(o,l),Array.isArray(o))for(let h of o)s(h,l);else if(o instanceof Xi){if(t.has(o.compartment))throw new RangeError("Duplicate use of compartment in extensions");let h=e.get(o.compartment)||o.inner;t.set(o.compartment,h),s(h,l)}else if(o instanceof Zn)s(o.inner,o.prec);else if(o instanceof he)i[l].push(o),o.provides&&s(o.provides,l);else if(o instanceof ni)i[l].push(o),o.facet.extensions&&s(o.facet.extensions,Lt.default);else{let h=o.extension;if(!h)throw new Error(`Unrecognized extension value in extension set (${o}).`);if(h==o)throw new Error(`Unrecognized extension value in extension set (${o}). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.`);s(h,l)}}return s(n,Lt.default),i.reduce((o,l)=>o.concat(l))}function Ai(n,e){if(e&1)return 2;let t=e>>1,i=n.status[t];if(i==4)throw new Error("Cyclic dependency between fields and/or facets");if(i&2)return i;n.status[t]=4;let r=n.computeSlot(n,n.config.dynamicSlots[t]);return n.status[t]=2|r}function Rn(n,e){return e&1?n.config.staticValues[e>>1]:n.values[e>>1]}var na=C.define(),Nr=C.define({combine:n=>n.some(e=>e),static:!0}),ra=C.define({combine:n=>n.length?n[0]:void 0,static:!0}),sa=C.define(),oa=C.define(),la=C.define(),aa=C.define({combine:n=>n.length?n[0]:!1}),pe=class{constructor(e,t){this.type=e,this.value=t}static define(){return new Gr}},Gr=class{of(e){return new pe(this,e)}},Ur=class{constructor(e){this.map=e}of(e){return new Y(this,e)}},Y=class n{constructor(e,t){this.type=e,this.value=t}map(e){let t=this.type.map(this.value,e);return t===void 0?void 0:t==this.value?this:new n(this.type,t)}is(e){return this.type==e}static define(e={}){return new Ur(e.map||(t=>t))}static mapEffects(e,t){if(!e.length)return e;let i=[];for(let r of e){let s=r.map(t);s&&i.push(s)}return i}};Y.reconfigure=Y.define();Y.appendConfig=Y.define();var ee=class n{constructor(e,t,i,r,s,o){this.startState=e,this.changes=t,this.selection=i,this.effects=r,this.annotations=s,this.scrollIntoView=o,this._doc=null,this._state=null,i&&ia(i,t.newLength),s.some(l=>l.type==n.time)||(this.annotations=s.concat(n.time.of(Date.now())))}static create(e,t,i,r,s,o){return new n(e,t,i,r,s,o)}get newDoc(){return this._doc||(this._doc=this.changes.apply(this.startState.doc))}get newSelection(){return this.selection||this.startState.selection.map(this.changes)}get state(){return this._state||this.startState.applyTransaction(this),this._state}annotation(e){for(let t of this.annotations)if(t.type==e)return t.value}get docChanged(){return!this.changes.empty}get reconfigured(){return this.startState.config!=this.state.config}isUserEvent(e){let t=this.annotation(n.userEvent);return!!(t&&(t==e||t.length>e.length&&t.slice(0,e.length)==e&&t[e.length]=="."))}};ee.time=pe.define();ee.userEvent=pe.define();ee.addToHistory=pe.define();ee.remote=pe.define();function ud(n,e){let t=[];for(let i=0,r=0;;){let s,o;if(i<n.length&&(r==e.length||e[r]>=n[i]))s=n[i++],o=n[i++];else if(r<e.length)s=e[r++],o=e[r++];else return t;!t.length||t[t.length-1]<s?t.push(s,o):t[t.length-1]<o&&(t[t.length-1]=o)}}function ha(n,e,t){var i;let r,s,o;return t?(r=e.changes,s=ae.empty(e.changes.length),o=n.changes.compose(e.changes)):(r=e.changes.map(n.changes),s=n.changes.mapDesc(e.changes,!0),o=n.changes.compose(r)),{changes:o,selection:e.selection?e.selection.map(s):(i=n.selection)===null||i===void 0?void 0:i.map(r),effects:Y.mapEffects(n.effects,r).concat(Y.mapEffects(e.effects,s)),annotations:n.annotations.length?n.annotations.concat(e.annotations):e.annotations,scrollIntoView:n.scrollIntoView||e.scrollIntoView}}function Fr(n,e,t){let i=e.selection,r=ri(e.annotations);return e.userEvent&&(r=r.concat(ee.userEvent.of(e.userEvent))),{changes:e.changes instanceof ae?e.changes:ae.of(e.changes||[],t,n.facet(ra)),selection:i&&(i instanceof b?i:b.single(i.anchor,i.head)),effects:ri(e.effects),annotations:r,scrollIntoView:!!e.scrollIntoView}}function ca(n,e,t){let i=Fr(n,e.length?e[0]:{},n.doc.length);e.length&&e[0].filter===!1&&(t=!1);for(let s=1;s<e.length;s++){e[s].filter===!1&&(t=!1);let o=!!e[s].sequential;i=ha(i,Fr(n,e[s],o?i.changes.newLength:n.doc.length),o)}let r=ee.create(n,i.changes,i.selection,i.effects,i.annotations,i.scrollIntoView);return Od(t?dd(r):r)}function dd(n){let e=n.startState,t=!0;for(let r of e.facet(sa)){let s=r(n);if(s===!1){t=!1;break}Array.isArray(s)&&(t=t===!0?s:ud(t,s))}if(t!==!0){let r,s;if(t===!1)s=n.changes.invertedDesc,r=ae.empty(e.doc.length);else{let o=n.changes.filter(t);r=o.changes,s=o.filtered.mapDesc(o.changes).invertedDesc}n=ee.create(e,r,n.selection&&n.selection.map(s),Y.mapEffects(n.effects,s),n.annotations,n.scrollIntoView)}let i=e.facet(oa);for(let r=i.length-1;r>=0;r--){let s=i[r](n);s instanceof ee?n=s:Array.isArray(s)&&s.length==1&&s[0]instanceof ee?n=s[0]:n=ca(e,ri(s),!1)}return n}function Od(n){let e=n.startState,t=e.facet(la),i=n;for(let r=t.length-1;r>=0;r--){let s=t[r](n);s&&Object.keys(s).length&&(i=ha(i,Fr(e,s,n.changes.newLength),!0))}return i==n?n:ee.create(e,n.changes,n.selection,i.effects,i.annotations,i.scrollIntoView)}var pd=[];function ri(n){return n==null?pd:Array.isArray(n)?n:[n]}var Pe=(function(n){return n[n.Word=0]="Word",n[n.Space=1]="Space",n[n.Other=2]="Other",n})(Pe||(Pe={})),md=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/,Hr;try{Hr=new RegExp("[\\p{Alphabetic}\\p{Number}_]","u")}catch{}function gd(n){if(Hr)return Hr.test(n);for(let e=0;e<n.length;e++){let t=n[e];if(/\w/.test(t)||t>"\x80"&&(t.toUpperCase()!=t.toLowerCase()||md.test(t)))return!0}return!1}function Sd(n){return e=>{if(!/\S/.test(e))return Pe.Space;if(gd(e))return Pe.Word;for(let t=0;t<n.length;t++)if(e.indexOf(n[t])>-1)return Pe.Word;return Pe.Other}}var D=class n{constructor(e,t,i,r,s,o){this.config=e,this.doc=t,this.selection=i,this.values=r,this.status=e.statusTemplate.slice(),this.computeSlot=s,o&&(o._state=this);for(let l=0;l<this.config.dynamicSlots.length;l++)Ai(this,l<<1);this.computeSlot=null}field(e,t=!0){let i=this.config.address[e.id];if(i==null){if(t)throw new RangeError("Field is not present in this state");return}return Ai(this,i),Rn(this,i)}update(...e){return ca(this,e,!0)}applyTransaction(e){let t=this.config,{base:i,compartments:r}=t;for(let l of e.effects)l.is(An.reconfigure)?(t&&(r=new Map,t.compartments.forEach((a,h)=>r.set(h,a)),t=null),r.set(l.value.compartment,l.value.extension)):l.is(Y.reconfigure)?(t=null,i=l.value):l.is(Y.appendConfig)&&(t=null,i=ri(i).concat(l.value));let s;t?s=e.startState.values.slice():(t=Xn.resolve(i,r,this),s=new n(t,this.doc,this.selection,t.dynamicSlots.map(()=>null),(a,h)=>h.reconfigure(a,this),null).values);let o=e.startState.facet(Nr)?e.newSelection:e.newSelection.asSingle();new n(t,e.newDoc,o,s,(l,a)=>a.update(l,e),e)}replaceSelection(e){return typeof e=="string"&&(e=this.toText(e)),this.changeByRange(t=>({changes:{from:t.from,to:t.to,insert:e},range:b.cursor(t.from+e.length)}))}changeByRange(e){let t=this.selection,i=e(t.ranges[0]),r=this.changes(i.changes),s=[i.range],o=ri(i.effects);for(let l=1;l<t.ranges.length;l++){let a=e(t.ranges[l]),h=this.changes(a.changes),c=h.map(r);for(let u=0;u<l;u++)s[u]=s[u].map(c);let f=r.mapDesc(h,!0);s.push(a.range.map(f)),r=r.compose(c),o=Y.mapEffects(o,c).concat(Y.mapEffects(ri(a.effects),f))}return{changes:r,selection:b.create(s,t.mainIndex),effects:o}}changes(e=[]){return e instanceof ae?e:ae.of(e,this.doc.length,this.facet(n.lineSeparator))}toText(e){return M.of(e.split(this.facet(n.lineSeparator)||Dr))}sliceDoc(e=0,t=this.doc.length){return this.doc.sliceString(e,t,this.lineBreak)}facet(e){let t=this.config.address[e.id];return t==null?e.default:(Ai(this,t),Rn(this,t))}toJSON(e){let t={doc:this.sliceDoc(),selection:this.selection.toJSON()};if(e)for(let i in e){let r=e[i];r instanceof he&&this.config.address[r.id]!=null&&(t[i]=r.spec.toJSON(this.field(e[i]),this))}return t}static fromJSON(e,t={},i){if(!e||typeof e.doc!="string")throw new RangeError("Invalid JSON representation for EditorState");let r=[];if(i){for(let s in i)if(Object.prototype.hasOwnProperty.call(e,s)){let o=i[s],l=e[s];r.push(o.init(a=>o.spec.fromJSON(l,a)))}}return n.create({doc:e.doc,selection:b.fromJSON(e.selection),extensions:t.extensions?r.concat([t.extensions]):r})}static create(e={}){let t=Xn.resolve(e.extensions||[],new Map),i=e.doc instanceof M?e.doc:M.of((e.doc||"").split(t.staticFacet(n.lineSeparator)||Dr)),r=e.selection?e.selection instanceof b?e.selection:b.single(e.selection.anchor,e.selection.head):b.single(0);return ia(r,i.length),t.staticFacet(Nr)||(r=r.asSingle()),new n(t,i,r,t.dynamicSlots.map(()=>null),(s,o)=>o.create(s),null)}get tabSize(){return this.facet(n.tabSize)}get lineBreak(){return this.facet(n.lineSeparator)||`
@@ -1,8 +1,8 @@
1
1
  (function (root, factory) {
2
- const api = factory();
2
+ const api = factory(typeof module === "object" && module.exports ? require("./markdown-document-model.js") : root?.WriterMarkdownDocumentModel);
3
3
  if (typeof module === "object" && module.exports) module.exports = api;
4
4
  if (root) root.WriterWorkbenchComponentAssetOperations = api;
5
- })(typeof globalThis !== "undefined" ? globalThis : this, function () {
5
+ })(typeof globalThis !== "undefined" ? globalThis : this, function (markdownModel) {
6
6
  "use strict";
7
7
 
8
8
  function characterAssetId(characterId) {
@@ -34,6 +34,18 @@
34
34
  const label = clue.name || clue.title || clue.id || index + 1;
35
35
  result[assetId].push(`단서 · ${label}`);
36
36
  }
37
+ for (const instance of active) {
38
+ if (instance.templateId === "ssobig.assets") continue;
39
+ const visit = (value, path = []) => {
40
+ if (typeof value === "string") {
41
+ const blocks = (markdownModel || globalThis.WriterMarkdownDocumentModel)?.parseDocument(value)?.blocks || [];
42
+ blocks.filter(block => block.type === "image").forEach(block => {
43
+ (result[block.assetId] ||= []).push(`${instance.tabLabel || instance.instanceId} · 본문 (${path.join(" / ")})`);
44
+ });
45
+ } else if (value && typeof value === "object") Object.entries(value).forEach(([key, child]) => visit(child, [...path, key]));
46
+ };
47
+ visit(dataFor(instance) || instance.data);
48
+ }
37
49
  return result;
38
50
  }
39
51
 
@@ -170,7 +170,15 @@
170
170
  component({
171
171
  templateId: "ssobig.timeline",
172
172
  identityPolicy: IDENTITY.UUID,
173
- root: shape({ events: field("array", { item: "event" }) }),
173
+ // 전환기 계약: 사건이 시간을 소유하는 events 항목이 시간을 소유하는 timeLevels/entries를 상호 배타로 허용한다.
174
+ // 두 모양의 배타성과 참조 검증은 component-storage-contract의 관계 검증이 담당한다.
175
+ root: shape({}, {
176
+ events: field("array", { item: "event" }),
177
+ timeLevels: field("array", { item: "timeLevel" }),
178
+ entries: field("array", { item: "entry" }),
179
+ // 작품의 장소 목록. 배열 순서가 표시 순서이며 v2 모양에서만 사용한다.
180
+ places: field("array", { item: "place" })
181
+ }),
174
182
  items: {
175
183
  event: shape({
176
184
  id: field("uuid", { visibility: "hidden" }), time: field("non-empty-string", { role: "display-name" }),
@@ -181,7 +189,44 @@
181
189
  values: "memory", keyFormat: "stable-key", keyRole: "unresolved-character-key"
182
190
  })
183
191
  }),
184
- memory: shape({ included: field("boolean"), text: field("string") })
192
+ memory: shape({ included: field("boolean"), text: field("string") }),
193
+ timeLevel: shape({
194
+ id: field("uuid", { role: "technical-identity", visibility: "hidden" }),
195
+ name: field("non-empty-string", { role: "display-name" }),
196
+ mode: field("enum", { values: ["manual", "clock"] })
197
+ }),
198
+ entry: shape({
199
+ id: field("uuid", { role: "technical-identity", visibility: "hidden" }),
200
+ timeLevelId: field("uuid", { references: "timeLevel.id" }),
201
+ lane: field("object", { item: "entryLane" }),
202
+ when: field("object", { item: "entryWhen" }),
203
+ timeLabel: field("string", { role: "display-name" }),
204
+ text: field("string", { role: "content" }),
205
+ included: field("boolean")
206
+ }, {
207
+ // location은 장소 목록으로 옮기는 동안 유지하는 전환기 fallback 문자열이다.
208
+ location: field("string"),
209
+ // 배열 순서는 항목의 이동 경로 순서다.
210
+ placeIds: field("unique-uuid-array", { references: "place.id" }),
211
+ eventGroupId: field("uuid", { role: "loose-grouping" })
212
+ }),
213
+ place: shape({
214
+ id: field("uuid", { role: "technical-identity", visibility: "hidden" }),
215
+ name: field("non-empty-string", { role: "display-name" })
216
+ }),
217
+ entryLane: shape({
218
+ kind: field("enum", { values: ["truth", "character", "unresolved"] })
219
+ }, {
220
+ characterId: field("stable-key", { references: "ssobig.character key" }),
221
+ sourceKey: field("stable-key", { keyRole: "unresolved-character-key" })
222
+ }),
223
+ entryWhen: shape({
224
+ kind: field("enum", { values: ["unspecified", "clock"] })
225
+ }, {
226
+ dayOffset: field("non-negative-integer"),
227
+ startMinute: field("integer", { minimum: 0, maximum: 1439 }),
228
+ endMinute: field("integer", { minimum: 0, maximum: 1439 })
229
+ })
185
230
  }
186
231
  }),
187
232
  component({
@@ -456,6 +501,38 @@
456
501
  }
457
502
  }
458
503
  function idSet(items) { return new Set(items.map(item => String(item.id || "").toLowerCase())); }
504
+ function timelineShapeError(data) {
505
+ const events = Array.isArray(data.events);
506
+ const levels = Array.isArray(data.timeLevels);
507
+ const entries = Array.isArray(data.entries);
508
+ if (events && (levels || entries)) return "$.events와 $.timeLevels/$.entries는 함께 사용할 수 없습니다.";
509
+ if (!events && !levels && !entries) return "$에는 $.events 또는 $.timeLevels와 $.entries가 있어야 합니다.";
510
+ if (!events && !(levels && entries)) return "$.timeLevels와 $.entries는 함께 있어야 합니다.";
511
+ // 장소 목록은 항목이 시간을 소유하는 v2 모양에서만 사용한다.
512
+ if (events && Object.hasOwn(data, "places")) return "$.places는 $.events 모양에서 사용할 수 없습니다.";
513
+ return null;
514
+ }
515
+ function timelineLaneError(lane, path) {
516
+ if (lane.kind === "character") {
517
+ if (!Object.hasOwn(lane, "characterId")) return `${path}.lane에 character 종류의 필수 값이 없습니다.`;
518
+ return Object.hasOwn(lane, "sourceKey") ? `${path}.lane.sourceKey 필드는 character 종류에서 사용할 수 없습니다.` : null;
519
+ }
520
+ if (lane.kind === "unresolved") {
521
+ if (!Object.hasOwn(lane, "sourceKey")) return `${path}.lane에 unresolved 종류의 필수 값이 없습니다.`;
522
+ return Object.hasOwn(lane, "characterId") ? `${path}.lane.characterId 필드는 unresolved 종류에서 사용할 수 없습니다.` : null;
523
+ }
524
+ const unexpected = ["characterId", "sourceKey"].find(name => Object.hasOwn(lane, name));
525
+ return unexpected ? `${path}.lane.${unexpected} 필드는 truth 종류에서 사용할 수 없습니다.` : null;
526
+ }
527
+ function timelineWhenError(when, level, path) {
528
+ if (when.kind === "clock") {
529
+ if (!Object.hasOwn(when, "startMinute")) return `${path}.when에 clock 종류의 필수 값이 없습니다.`;
530
+ if (Object.hasOwn(when, "endMinute") && when.endMinute < when.startMinute) return `${path}.when.endMinute은 startMinute보다 앞설 수 없습니다.`;
531
+ return level.mode === "clock" ? null : `${path}.when은 clock 모드 시간 레벨에서만 사용할 수 있습니다.`;
532
+ }
533
+ const unexpected = ["dayOffset", "startMinute", "endMinute"].find(name => Object.hasOwn(when, name));
534
+ return unexpected ? `${path}.when.${unexpected} 필드는 unspecified 종류에서 사용할 수 없습니다.` : null;
535
+ }
459
536
  function validateRelations(contract, data) {
460
537
  switch (contract.templateId) {
461
538
  case "ssobig.basic":
@@ -553,6 +630,25 @@
553
630
  }
554
631
  return null;
555
632
  }
633
+ case "ssobig.timeline": {
634
+ const shapeError = timelineShapeError(data);
635
+ if (shapeError || Array.isArray(data.events)) return shapeError;
636
+ // 배열 항목 id 유일성(places 포함)은 array field 검증이 먼저 확인하므로 여기서는 참조와 종류별 규칙만 본다.
637
+ const levels = new Map(data.timeLevels.map(level => [level.id, level]));
638
+ const places = idSet(Array.isArray(data.places) ? data.places : []);
639
+ for (let index = 0; index < data.entries.length; index += 1) {
640
+ const entry = data.entries[index];
641
+ const path = `$.entries.${index}`;
642
+ const level = levels.get(entry.timeLevelId);
643
+ if (!level) return `${path}.timeLevelId가 존재하지 않는 시간 레벨을 가리킵니다.`;
644
+ const invalid = timelineLaneError(entry.lane, path) || timelineWhenError(entry.when, level, path);
645
+ if (invalid) return invalid;
646
+ if (Array.isArray(entry.placeIds) && entry.placeIds.some(id => !places.has(String(id).toLowerCase()))) {
647
+ return `${path}.placeIds가 존재하지 않는 장소를 가리킵니다.`;
648
+ }
649
+ }
650
+ return null;
651
+ }
556
652
  case "ssobig.ending": {
557
653
  const conditions = idSet(data.conditions), outcomes = idSet(data.outcomes);
558
654
  if (conditions.size !== outcomes.size || [...conditions].some(id => !outcomes.has(id))) return "$.conditions와 $.outcomes는 같은 id로 정확히 1:1 연결되어야 합니다.";
@@ -21,6 +21,11 @@
21
21
  .filter(id => !deleted.has(id) && Object.hasOwn(characters, id)).length;
22
22
  }
23
23
 
24
+ // v2는 항목이, legacy는 사건이 세는 단위다.
25
+ function timelineCount(data) {
26
+ return Array.isArray(data?.entries) ? data.entries.length : listLength(data?.events);
27
+ }
28
+
24
29
  function endingCount(data) {
25
30
  const conditions = Array.isArray(data?.conditions) ? data.conditions : [];
26
31
  const outcomes = Array.isArray(data?.outcomes) ? data.outcomes : [];
@@ -40,7 +45,7 @@
40
45
  case "ssobig.clues": return listLength(data?.clues);
41
46
  case "ssobig.clue-combinations": return listLength(data?.combinations);
42
47
  case "ssobig.ai-response": return listLength(data?.responses);
43
- case "ssobig.timeline": return listLength(data?.events);
48
+ case "ssobig.timeline": return timelineCount(data);
44
49
  case "ssobig.investigation-board": return listLength(data?.nodes);
45
50
  case "ssobig.ending": return endingCount(data);
46
51
  case "ssobig.author-notes": return listLength(data?.notes);
@@ -123,20 +123,39 @@
123
123
  || (component.internal && instance.config?.hidden !== true ? "에셋 Component는 내부 전용으로 숨김 처리되어야 합니다." : null);
124
124
  }
125
125
 
126
+ function legacyTimelineError(timeline, characterIds) {
127
+ for (const event of timeline.data.events) {
128
+ const missing = Object.keys(event.memories).find(id => !characterIds.has(id));
129
+ if (missing) return `${timeline.instanceId} timeline memory가 존재하지 않는 character(${missing})를 가리킵니다.`;
130
+ const unassigned = Object.keys(event.unassignedMemories || {});
131
+ const assigned = unassigned.find(id => characterIds.has(id));
132
+ if (assigned) return `${timeline.instanceId} unassigned memory(${assigned})는 현재 character에 연결할 수 있습니다.`;
133
+ const overlap = unassigned.find(id => Object.hasOwn(event.memories, id));
134
+ if (overlap) return `${timeline.instanceId} memory(${overlap})가 assigned와 unassigned에 중복 저장되어 있습니다.`;
135
+ }
136
+ return null;
137
+ }
138
+
139
+ function timelineLaneCharacterError(timeline, characterIds) {
140
+ for (const entry of timeline.data.entries) {
141
+ if (entry.lane.kind === "character" && !characterIds.has(entry.lane.characterId)) {
142
+ return `${timeline.instanceId} timeline 항목이 존재하지 않는 character(${entry.lane.characterId})를 가리킵니다.`;
143
+ }
144
+ }
145
+ return null;
146
+ }
147
+
126
148
  function compositionRelationError(instances) {
127
149
  const character = instances.find(instance => instance.templateId === "ssobig.character");
128
150
  if (character) {
129
151
  const characterIds = new Set(Object.keys(character.data.characters || {}));
152
+ // 타임라인의 모양 배타, 시간 레벨 참조, lane/when 종류별 규칙은 validateComponentData가 이미 검증한다.
153
+ // 여기서는 다른 Component를 봐야 판정할 수 있는 character 참조만 확인한다.
130
154
  for (const timeline of instances.filter(instance => instance.templateId === "ssobig.timeline")) {
131
- for (const event of timeline.data.events) {
132
- const missing = Object.keys(event.memories).find(id => !characterIds.has(id));
133
- if (missing) return `${timeline.instanceId} timeline memory가 존재하지 않는 character(${missing})를 가리킵니다.`;
134
- const unassigned = Object.keys(event.unassignedMemories || {});
135
- const assigned = unassigned.find(id => characterIds.has(id));
136
- if (assigned) return `${timeline.instanceId} unassigned memory(${assigned})는 현재 character에 연결할 수 있습니다.`;
137
- const overlap = unassigned.find(id => Object.hasOwn(event.memories, id));
138
- if (overlap) return `${timeline.instanceId} memory(${overlap})가 assigned와 unassigned에 중복 저장되어 있습니다.`;
139
- }
155
+ const invalid = Array.isArray(timeline.data.events)
156
+ ? legacyTimelineError(timeline, characterIds)
157
+ : timelineLaneCharacterError(timeline, characterIds);
158
+ if (invalid) return invalid;
140
159
  }
141
160
  }
142
161
  const clues = instances.find(instance => instance.templateId === "ssobig.clues");