สัญญาของเอกสารหน้า (page-document contract)
บันทึกสำหรับนักพัฒนาของ backend/src/lib/page-contract ไม่ใช่ specification ขอบเขตของผลิตภัณฑ์คือ
SPEC.md §4.2 และ §10 สัญญาของ renderer ที่ export ออกไปคือ SPEC.md §10.2 ขอบเขตของ renderer
ที่อ่านผ่าน API แบบ live คือ SPEC-RENDERER.md §5.1 และ §6
มันคืออะไร
หัวข้อที่มีชื่อว่า “มันคืออะไร”นิยามเดียวของเอกสารหน้า Dee Wan ที่ไม่มี dependency และไม่ผูกกับ framework ใด โมดูลนี้ export ค่าคงที่ของรูปแบบ (format) คำศัพท์ของ slide, type ที่แน่นอนของ slide และเอกสาร, ขีดจำกัด, parser, รหัสการปฏิเสธ และชุด fixture สำหรับทดสอบความสอดคล้อง (conformance)
backend/src/lib/page-contract/ refusal.ts DOCUMENT_REFUSALS, Refusal, Parsed, refuse, accept document.ts PAGE_FORMAT, SLIDE_TYPES, LIMITS, types, parsers fields.ts SLIDES_FIELD_KEY, TITLE_FIELD_KEY, SLIDES_FIELD_TYPE, isPageModel fixtures.ts ACCEPTED_PAGES, REFUSED_PAGES, REFUSED_MODEL_SLIDES, STORED_DELETED_MEDIA format2.ts PAGE_FORMAT_2, PAGE_FORMATS, SECTION_LIMITS, section types renderer.ts renderablePage, parseAnyPageDocument, parsePublicRenderablePage, declaredFormat migrate.ts migrateGroupToFormat2 group-structure.ts structureDrift, requireSameStructure, groupStructure, localeSaveKeepsStructure index.ts barrel — everything except the fixturesfixture ถูกแยกไว้นอก barrel โดยตั้งใจ เพราะเป็นข้อมูลทดสอบ และ barrel ที่ดึง fixture เข้ามาด้วย จะทำให้ fixture ทุกตัวถูกรวมเข้าไปในทุก bundle ที่อ่านหน้าเพียงหน้าเดียว
ใครเป็นเจ้าของอะไร
หัวข้อที่มีชื่อว่า “ใครเป็นเจ้าของอะไร”| ชั้น | เป็นเจ้าของ |
|---|---|
page-contract |
รูปแบบเอกสาร, คำศัพท์ของ slide, ขีดจำกัด, parser, รหัสการปฏิเสธ |
page-composer |
operation, การวางแผน, prompt ของโมเดล, นโยบายการเขียนของ admin (COMPOSER_OWNED_FIELD_KEYS) |
api/pages.ts |
การตรวจสิทธิ์, การสร้าง media lookup, การเขียนตาม lifecycle, การแปลงเป็น HTTP status |
frontend/src/lib/pages/slides.ts |
การแสดงผลเท่านั้น — ป้ายชื่อ section และ projection ของ outline |
| renderer ภายนอก | HTML, CSS, layout, routing, response header |
page-composer/errors.ts สร้าง COMPOSER_ERRORS เป็น [...DOCUMENT_REFUSALS, ...composer-only]
รหัสของเอกสารจะไม่ถูกประกาศซ้ำที่นั่นเลย
ใครใช้มัน
หัวข้อที่มีชื่อว่า “ใครใช้มัน”- Backend:
page-composer/operations.ts,page-composer/planner.ts,api/pages.ts,lib/lifecycle/service.ts - Frontend:
src/lib/pages/slides.tsre-export มันผ่าน relative path (../../../../backend/src/lib/page-contract) แบบเดียวกับที่ type ของ$prisma/generatedเข้าถึง แอป admin ไม่มีอะไรถูกคัดลอกด้วยมือ
ไม่มีชุดคำศัพท์ที่เขียนด้วยมือชุดที่สาม และ frontend/cypress/specs/page-composer-parity.test.ts
จะ fail หากมีชุดนั้นเกิดขึ้น
จุดเข้าของ parser และขอบเขตความเชื่อถือ
หัวข้อที่มีชื่อว่า “จุดเข้าของ parser และขอบเขตความเชื่อถือ”ทั้งสามตัว parse เอกสารเดียวกัน สิ่งที่ต่างกันคือกฎของ media และความหมายของเอกสารที่ไม่มีอยู่
| จุดเข้า | ที่มา | Media | เอกสารที่ไม่มีอยู่ | ผู้เรียก |
|---|---|---|---|---|
parseSlide(raw, id, {origin:'model', media}) |
คำตอบของ language model | resolve ผ่าน MediaLookup ของไซต์ แล้วตรวจค่าที่ resolve ได้กับสัญญาของ media ที่บันทึกไว้ id ที่ resolve ไม่ได้คือ unknown_media |
ไม่เกี่ยวข้อง | การสร้างและการแก้ไข |
parsePageDocument(raw, {origin:'stored', media}) |
เอกสารที่ผลิตภัณฑ์นี้เขียน | resolve ผ่าน lookup แถวที่ resolve ได้แต่ไม่ผ่านสัญญา และแถวที่ถูกลบ จะถอยกลับไปใช้ reference ที่บันทึกไว้ทั้งคู่ | หน้าว่าง | composer ของ admin |
parsePublicPageDocument(raw) |
fields.slides ของ envelope สาธารณะ |
ไม่มี lookup เลย — media_id, url และ alt ที่บันทึกไว้จะถูกตรวจตามที่เป็นอยู่ |
unreadable_page |
renderer |
ทุกค่าที่ส่งถึงตัวใดตัวหนึ่งในนี้ถือว่าไม่น่าเชื่อถือ
Invariant: สิ่งที่ composer เขียน renderer ต้องอ่านได้
หัวข้อที่มีชื่อว่า “Invariant: สิ่งที่ composer เขียน renderer ต้องอ่านได้”ทุกเอกสารที่ create หรือ revise บันทึกลงไปต้องผ่าน parsePublicPageDocument มีแนวป้องกันอิสระสองชั้น
ที่รักษาข้อนี้ไว้ และ test/page-contract-publishable.test.ts พิสูจน์แต่ละชั้นแยกกัน:
- Media ที่ resolve แล้วจะถูกตรวจ
MediaLookupตอบจากmedia.urlซึ่งเป็น string ที่เก็บไว้ การ import สามารถเขียนอะไรก็ได้ลงไป —media-import.tsตรวจเพียงว่าsource_urlไม่ว่าง — ดังนั้นแถวที่เก็บjavascript:alert(1)เคยทำให้เกิดหน้าที่บันทึกแล้วแต่ public parser ปฏิเสธ ตอนนี้ media ที่มาจากโมเดล ถูกตรวจกับสัญญาของ media ที่บันทึกไว้ชุดเดียวกับที่การอ่านแบบสาธารณะใช้ และหน้าจะถูก ปฏิเสธ ไม่ใช่ถูกตัดรูปภาพทิ้ง - เอกสารทั้งฉบับจะถูกอ่านซ้ำก่อนการเขียนทุกครั้ง
api/pages.tsรันparsePublicPageDocumentกับเอกสารที่สร้างขึ้นทั้งใน handler ของ create และ revise ก่อนsaveContentหากไม่ผ่าน จะไม่มีอะไรถูกบันทึก
ชั้นที่สองซ้ำซ้อนตราบใดที่ชั้นแรกถูกต้อง และนั่นคือประเด็น: การ mutate parser อย่างเดียว route ยังคงปฏิเสธอยู่ ต้อง mutate ทั้งสองชั้นจึงจะปล่อยหน้าที่ไม่ดีผ่านไปได้
ผลที่ต้องรู้ไว้ หน้าที่ถูกบันทึกไว้ก่อนกฎนี้มีอยู่ และมีแถว media ที่ใช้ไม่ได้ ยังคง เปิด ได้ใน admin — มิฉะนั้นจะไม่มีทางแก้ไขให้เป็นหน้าที่ถูกต้องได้เลย — แต่จะ บันทึก ไม่ได้จนกว่าจะแก้ media หรือลบ slide นั้นออก การปฏิเสธจะระบุชื่อฟิลด์ นี่เป็นความตั้งใจ: ทางเลือกอื่นคือการทิ้งรูปภาพที่ผู้แก้ไขไม่ได้ขอให้ลบ อย่างเงียบ ๆ
ทำไม public parser รับ argument เดียว
หัวข้อที่มีชื่อว่า “ทำไม public parser รับ argument เดียว”การ render หน้าที่เผยแพร่แล้วต้องไม่ต้องการฐานข้อมูลของ admin เลย พารามิเตอร์ media-lookup คือ
ฐานข้อมูลที่ปลอมตัวมา: ทันทีที่ signature มีพารามิเตอร์นี้ renderer ก็ต้องมีบางอย่างส่งเข้าไป
test/page-contract-boundary.test.ts assert ว่า parsePublicPageDocument.length === 1
จุดที่ model, stored และ public ต่างกันโดยตั้งใจ
หัวข้อที่มีชื่อว่า “จุดที่ model, stored และ public ต่างกันโดยตั้งใจ”- Media ที่ไม่รู้จัก มีเพียง
'model'ที่ทำให้เกิดunknown_mediaได้ นี่คือตัวป้องกันข้าม tenant สำหรับ media ของ slide: lookup สร้างจากแถว media ของไซต์นี้ ดังนั้น id จากที่อื่นใดจะ resolve ไม่ได้อะไรเลย - แถว media ที่ถูกลบ
'stored'เก็บสิ่งที่เอกสารบันทึกไว้ ดังนั้นการลบรูปภาพหนึ่งรูปจะไม่ทำให้หน้าที่มีอยู่ อ่านไม่ได้และจึงแก้ไขไม่ได้'public'อ่านค่าที่บันทึกไว้ชุดเดียวกันและได้ media เดียวกัน — เมื่อแถวหายไป แล้ว เอกสารคือแหล่งข้อมูลเดียวที่ทั้งสองฝั่งมี - ออบเจกต์ media ที่บันทึกไว้แต่ผิดรูป
'stored'เก็บสิ่งที่เอกสารถืออยู่และไม่เคยปฏิเสธ ดังนั้นหน้าที่ เสียหายยังคงเปิดได้และจึงแก้ไขได้ใน admin'public'ปฏิเสธทั้งหน้า ความต่างนี้เป็นความตั้งใจ และเป็น จุดเดียวที่การอ่านทั้งสองแบบต่างกัน; ด่านตรวจก่อนบันทึกข้างต้นคือสิ่งที่ป้องกันไม่ให้มันกลายเป็นช่องทาง บันทึกหน้าที่ render ไม่ได้ - เอกสารที่ไม่มีอยู่
'stored'อ่านเป็นหน้าว่าง — หน้าที่รอการสร้าง'public'ปฏิเสธ: การ render เอกสารว่างจะทำให้เกิดหน้าเปล่าที่ index ได้บนไซต์ที่ใช้งานจริงภายใต้ status 200
Media อยู่ตรงไหนใน slide
หัวข้อที่มีชื่อว่า “Media อยู่ตรงไหนใน slide”SLIDE_MEDIA_KEYS ประกาศ key ที่ค่าเป็น SlideMedia ตามแต่ละประเภทของ slide พร้อม accessor
ที่อ่านค่านั้น pageMediaReferences(document) สร้างมาจากสิ่งนี้ และคืน media reference ทุกตัว
ที่เอกสารมี พร้อม id ของ slide, ประเภทของ slide และ key ที่พบ
Graduation คือผู้เรียกที่ต้องใช้สิ่งนี้ การตรวจการดูแล media (media-custody) ของมันสแกนคอลัมน์
<key>_id ที่สร้างขึ้น และ media ของหน้าไม่ได้อยู่ในคอลัมน์ — มันอยู่ในเอกสาร slides — ดังนั้นหน้าที่มี
media แต่ถูกอ่านว่าไม่มี media จะทำให้ export ไซต์ที่รูปภาพ resolve ได้เฉพาะกับ CMS เท่านั้น
การประกาศนี้เข้าถึงได้เฉพาะ key ของ slide ระดับ บนสุด ประเภท slide ที่ซ้อน media reference ไว้ใน
list ต้องขยาย shape แทนที่จะประกาศ key ของ list; การทดสอบ corpus จะไล่ดูเอกสารที่ parse แล้ว
เพื่อหาคู่ media_id/url ใดก็ตามที่การประกาศเข้าไม่ถึง
กฎของ URL ของ media
หัวข้อที่มีชื่อว่า “กฎของ URL ของ media”URL ของ media ที่บันทึกไว้จะถูก parse ด้วย new URL ไม่ใช่เทียบ prefix startsWith('https://')
ไม่ใช่การตรวจ URL: มันยอมรับ https:// ที่ไม่มี host, https://user:pass@cdn.example/a.jpg และ —
เพราะเบราว์เซอร์ normalise backslash เป็น slash — /\evil.example/a.jpg ซึ่ง resolve จาก
https://site.example ไปเป็น https://evil.example/a.jpg
| รูปแบบ | ผลลัพธ์ |
|---|---|
https://cdn.example/a.jpg (อนุญาต query และ fragment) |
ยอมรับ |
/local-media/abc — มี slash นำหน้าเพียงตัวเดียวพอดี |
ยอมรับ |
http://localhost…, http://127.0.0.1…, http://[::1]… |
ยอมรับ |
http:// กับ host อื่นใด |
ปฏิเสธ — mixed content บนหน้าที่เผยแพร่ |
https:// ที่ไม่มี host |
ปฏิเสธ |
https://user:pass@host/a.jpg |
ปฏิเสธ — credential รั่วผ่าน referrer และ cache |
//evil.example/a.jpg |
ปฏิเสธ — protocol-relative อยู่นอก origin |
/\evil.example/a.jpg หรือ backslash ใดก็ตาม |
ปฏิเสธ |
javascript:, data:, scheme อื่นใด |
ปฏิเสธ |
มี whitespace หรือ markup แทรกอยู่, ยาวเกิน LIMITS.href |
ปฏิเสธ |
HTTP แบบ loopback ได้รับอนุญาตเพียงเพราะ local rig ให้บริการ media จาก
http://localhost:8787/local-media/…; media ใน production ต้องเป็น HTTPS หรือ site-relative
Alt text ถูกจำกัดขนาดและตรวจ type แต่ ไม่ ตรวจ markup มันคือคำบรรยาย media ที่ผู้เขียนเขียนเอง ไม่ใช่ผลลัพธ์จากโมเดล และลงไปอยู่ใน attribute ที่ escape แล้ว; การปฏิเสธหน้าเพราะวงเล็บมุมในคำบรรยาย จะทำให้ไซต์พังเพราะเครื่องหมายวรรคตอน
ลิงก์นำทางเข้มงวดกว่า URL ของ media: site-relative, https:// หรือ mailto: เท่านั้น
การอ่านเอกสารใน admin
หัวข้อที่มีชื่อว่า “การอ่านเอกสารใน admin”frontend/src/lib/pages/slides.ts export readPageDocument(value) ซึ่งคืน
{ ok: true, document } หรือ { ok: false, code, message }
มันไม่ใช่ type predicate โดยตั้งใจ guard แบบ value is PageDocument นั้น unsound: parser
ยอมรับ shape มากกว่าที่มันผลิต — string JSON ที่ serialize แล้ว และเอกสารที่ละ key ที่ไม่บังคับไว้
แทนที่จะเป็น null — และ normalise ให้ การรายงาน true เกี่ยวกับค่าต้นฉบับทำให้ compiler เข้าใจว่า
string มี .slides
มีเพียง parsed.value ที่มี shape ตามที่สัญญาไว้ ดังนั้น fetch-store/pages.ts จึงแทนที่ document
ของ response เองด้วยตัวที่ parse แล้ว ในการอ่านและเขียนหน้าทุกครั้ง เพื่อไม่ให้หน้าจอใดอ่าน slide
ที่ยังไม่ normalise
การปฏิเสธคือค่า
หัวข้อที่มีชื่อว่า “การปฏิเสธคือค่า”การปฏิเสธคือ { ok: false, error: { code, message, detail } } — ออบเจกต์ธรรมดา ไม่ใช่ error ที่ถูก
throw และไม่ใช่ class การตรวจ instanceof ข้ามขอบเขตของ package จะล้มเหลวเมื่อมี class เดียวกัน
สองสำเนา และ error ที่ถูก throw ไม่รอดผ่าน JSON api/pages.ts แปลงรหัสเป็น status;
frontend แปลงเป็นข้อความใน COMPOSER_REFUSAL_COPY
การเพิ่มประเภท slide
หัวข้อที่มีชื่อว่า “การเพิ่มประเภท slide”- เพิ่มลงใน
SLIDE_TYPESเพิ่ม type ที่แน่นอนของมัน และเพิ่มลงใน unionSlide - เพิ่ม entry ใน
SLIDE_KEYS,SLIDE_OPTIONAL_KEYS,SLIDE_VARIANT_KEYSและSLIDE_MEDIA_KEYS— ทั้งสี่ตัวใช้SlideTypeเป็น key ดังนั้นขั้นตอนนี้จะเป็น compile error จนกว่า จะทำเสร็จSLIDE_MEDIA_KEYSถูกตรวจเข้มกว่าตัวอื่น: ประเภท slide ที่ถือSlideMediaห้ามประกาศ[]และ key ที่ไม่ใช่ media ห้ามประกาศเลย - เพิ่ม
caseในparseSlide - เพิ่ม fixture ใน
ACCEPTED_PAGESที่ครอบคลุมประเภทนั้น โดยมีค่าที่ไม่บังคับครบทุกค่า, ค่าที่ไม่บังคับ เป็น null ทุกค่า และค่า enum ครบทุกค่า - เพิ่มป้ายชื่อใน
SLIDE_LABELSและ branch ในแต่ละ projection ในfrontend/src/lib/pages/slides.ts(slideOwnHeading,slideLead,slideParagraphs,slideFeatures,slideMedia,slideLink) — ทุกตัวเป็น switch แบบ exhaustive ดังนั้นนี่ก็เป็น compile error เช่นกันจนกว่าจะทำเสร็จ - เพิ่ม component ของ renderer ใน repository ของ renderer
ขั้นตอนที่ 1–5 จะ fail อย่างชัดเจนหากข้ามไป test/page-contract-corpus.test.ts fail กับประเภทที่ไม่มี
fixture; frontend/src/lib/pages/slides.test.ts fail กับประเภทที่ admin ไม่วาดอะไรให้;
PagePreview.svelte.test.ts fail กับประเภทที่ไม่ไปถึง card ใดเลย;
test/page-contract-media.test.ts fail กับเอกสารที่ถือ media reference ที่การประกาศเข้าไม่ถึง
ซึ่งเป็นกรณีที่การประกาศเฉพาะระดับบนสุดจะพลาดไป
ประเภท slide ใหม่ ไม่ ต้องเพิ่มเลข format การเพิ่มประเภทขยายสิ่งที่เก็บได้ แต่ไม่ได้เปลี่ยนวิธีอ่าน เอกสารที่มีอยู่
การเปลี่ยน PAGE_FORMAT
หัวข้อที่มีชื่อว่า “การเปลี่ยน PAGE_FORMAT”format คือขอบเขตของ migration parsePageDocument ปฏิเสธทุกอย่างที่ไม่ใช่ PAGE_FORMAT พอดี
และ parseAnyPageDocument ปฏิเสธทุกอย่างที่อยู่นอก PAGE_FORMATS — format ที่ไม่มีและ format
ที่ใหม่กว่าจะ fail closed การยอมรับ format ที่ใหม่กว่าจะเป็นการอ่านฟิลด์ของ build ที่ใหม่กว่าด้วยกฎของวันนี้
ทิ้งสิ่งที่ไม่รู้จัก และเขียนผลการอ่านที่สูญเสียข้อมูลนั้นกลับทับเอกสารจริง
การเพิ่มเลข format ต้องมีสิ่งต่อไปนี้ตามลำดับ:
- migration ของ core ที่เขียนเอกสารที่เก็บไว้ใหม่ หรือการตัดสินใจเรื่องความเข้ากันได้ที่เขียนไว้อย่างชัดเจน;
- อัปเดตสัญญา;
- ออก release ที่ renderer รองรับ;
- conformance fixture สำหรับทั้งสอง format;
- ลำดับการอัปเกรดที่มีเอกสารไว้สำหรับผู้รับไปดูแล (adopter)
อย่าเพิ่มเลข PAGE_FORMAT เพื่อเพิ่มฟิลด์หรือประเภท slide
PageDocument.format มี type เป็น typeof PAGE_FORMAT ไม่ใช่ number ดังนั้น { format: 999, slides: [] }
เป็นทั้ง compile error และการปฏิเสธตอน runtime test/page-contract-corpus.test.ts รักษาข้อนี้ไว้ด้วย
บรรทัด @ts-expect-error ที่จะ compile ไม่ผ่านหาก type ถูกขยายกว้างขึ้นอีก
การดูแล corpus
หัวข้อที่มีชื่อว่า “การดูแล corpus”fixtures.ts เขียนด้วยมือโดยตั้งใจ test/page-contract-corpus.test.ts อ่าน SLIDE_TYPES,
SLIDE_OPTIONAL_KEYS, SLIDE_VARIANT_KEYS, LIMITS และ DOCUMENT_REFUSALS และจะ fail จนกว่า
corpus จะทดสอบครบทุกตัว:
- ทุกประเภท slide ปรากฏในเอกสารที่ยอมรับและ parse แล้ว;
- ทุก key ที่ไม่บังคับปรากฏทั้งแบบไม่เป็น null และเป็น null;
- ทุก key แบบ enum ปรากฏครบทุกค่า;
- ทุกรหัสการปฏิเสธถูกผลิตโดย fixture ที่ถูกปฏิเสธบางตัว;
- ทุกขีดจำกัดถูกข้ามโดย fixture ที่ถูกปฏิเสธซึ่ง detail ของการปฏิเสธมี
maxค่านั้นพอดี
ความครอบคลุมวัดจากสิ่งที่ parser คืนมา ไม่ใช่จากสิ่งที่ fixture อ้าง corpus ที่สร้างจาก list เหล่านั้น จะตรงกับมันโดยโครงสร้างและไม่มีทาง fail ได้ — อย่าเขียนแบบนั้น
ACCEPTED_PAGES และ REFUSED_PAGES ถูกตัดสินโดย parsePublicPageDocument พฤติกรรมของที่มาแบบ
model และแบบ stored ถูกแยกไว้ใน REFUSED_MODEL_SLIDES และ STORED_DELETED_MEDIA
ความเข้ากันได้กับ workerd
หัวข้อที่มีชื่อว่า “ความเข้ากันได้กับ workerd”สัญญานี้ต้องรันใน workerd ได้โดยไม่มี CMS อยู่รอบ ๆ มัน import ได้เฉพาะไฟล์ข้างเคียงของตัวเองเท่านั้น
— ไม่มี Hono, ไม่มี Prisma, ไม่มี Svelte, ไม่มี Astro, ไม่มีโมดูล node:, ไม่มีโค้ดฐานข้อมูล
test/page-contract-boundary.test.ts assert ข้อนี้สองครั้งอย่างเป็นอิสระต่อกัน:
- อ่าน module specifier ทุกตัวในไดเรกทอรีและกำหนดให้แต่ละตัวต้องตรงกับ
^\./[A-Za-z0-9_-]+$; - bundle
index.tsด้วย esbuild ที่platform: 'neutral'หลัง resolver plugin ที่ปฏิเสธ specifier ที่ไม่ใช่ relative ทุกตัว แล้วตรวจผลลัพธ์หาrequire(และnode:
ไม่มีตัวใดรัน workerd จริง เมื่อรวมกันแล้วพิสูจน์ว่าโมดูลเป็นอิสระในตัวเอง ซึ่งเป็นคุณสมบัติที่ build
ของ workerd ต้องการ
renderer ภายนอกจะใช้มันอย่างไรในภายหลัง
หัวข้อที่มีชื่อว่า “renderer ภายนอกจะใช้มันอย่างไรในภายหลัง”ปัจจุบันสัญญาอยู่ใน repository นี้และถูกใช้ผ่าน relative path repository ของ renderer จะใช้ artifact ของไดเรกทอรีเดียวกันที่ถูก pin และเปลี่ยนแปลงไม่ได้ และต้องไม่คัดลอก type ด้วยมือเด็ดขาด การอัปเกรด สัญญาจึงจะกลายเป็นการเปลี่ยน dependency อย่างชัดแจ้ง
ชื่อของ artifact, ตำแหน่ง และกลไกการเผยแพร่ ยังไม่ได้ตัดสินใจ และต้องได้อำนาจจากเจ้าของโปรเจกต์ ไม่มีสิ่งใดใน repository นี้ระบุชื่อเหล่านั้น จนกว่าจะมีสิ่งเหล่านั้น ให้ถือว่า surface ที่ export ออกไปเปลี่ยนแปลงไม่ได้แล้ว: การเปลี่ยนแปลงแบบเพิ่มเติมมีต้นทุนต่ำ การเปลี่ยนชื่อและการเปลี่ยน shape ไม่ใช่
renderer ต้องการสิ่งจาก artifact เพียงสามอย่างพอดี:
parsePublicPageDocument— การตรวจสอบทั้งหมด, argument เดียว, ไม่มีฐานข้อมูล;SLIDE_TYPESและ type ของ slide — สำหรับ component registry ตอน build ที่ exhaustive ตอน compile;ACCEPTED_PAGES/REFUSED_PAGES— เพื่อพิสูจน์ว่า parser ของตัวเองเห็นตรงกับของ core
มันไม่ควร import page-composer เพื่อสิ่งใดเลย