העורך הציבורי (/markdown-editor)

עמוד ציבורי, בלי התחברות, לגולשים שמחפשים עורך Markdown: העורך מימין, התצוגה החיה משמאל, ושורה של כלי Markdown מעליהם. בכניסה הראשונה העורך מציג טקסט הדגמה, והטקסט שכותבים נשמר בדפדפן ומחכה בביקור הבא. הראוט הוא markdown_editor ב-webapp/app.py, הנתונים שהעמוד נשען עליהם ב-webapp/markdown_editor.py, התבנית webapp/templates/markdown_editor.html, והסקריפט של העמוד webapp/static/js/markdown-editor.js. העמוד לא דורש התחברות ופטור ממגבלת הקצב הגורפת, כמו /gallery (גלריית צילומי המסך (/gallery)).

הערכה

  • ערכה אחת לכל גולש. העמוד מוצג בערכה הציבורית ש-THEME_ID ב-webapp/markdown_editor.py מצביע עליה, גם למשתמש מחובר שבחר ערכה אחרת לשאר האתר. הראוט טוען אותה כמו ערכה ציבורית של מבקר (_load_shared_theme_for_request בלי משתמש, דרך הקאש), ומעביר אותה ל-render_template במפורש: ערך שמועבר שם גובר על מה ש-inject_globals מחזיר.

  • ערכה מותאמת של המשתמש מאופסת (custom_theme), כי צבעי התחביר שלה כתובים על [data-theme-type="custom"], שחל גם על ערכה ציבורית.

  • כשהערכה חסרה — נמחקה, כובתה, או שהמסד לא זמין — העמוד עולה בערכה הרגילה של האתר, ונרשמת אזהרה בלוג.

  • מגבלה ידועה: הסנכרון בין טאבים ב-base.html מחיל ערכה שהוחלפה בטאב אחר גם על העמוד הזה, עד שהוא נטען מחדש.

טקסט ההדגמה

  • הקובץ הוא webapp/markdown_editor_demo.md, עותק של המסמך CodeKeeper_Markdown.md ב-CodeKeeper, בלי הכותרות H5 ו-H6, שהתצוגה החיה לא מרנדרת (ראו ”התצוגה החיה בלי ובלי H5/H6“ למטה). העמוד מציג את העותק שבריפו: שינוי במסמך ב-CodeKeeper לא מגיע לעמוד בלי עדכון של הקובץ.

  • הקריאה ב-demo_text(), בבקשה הראשונה ולא בזמן ייבוא, ונשמרת בזיכרון של התהליך. lru_cache שומר רק ערך שהוחזר, ולכן קובץ שחסר בבקשה אחת יימצא בבאה.

  • הוא עובר לתבנית כמשתנה, ו-Jinja מברח אותו לתוך ה-textarea. לא {% include %}: בטקסט יש {{ __html: html }}, ו-include היה מריץ אותו כתבנית.

  • ”טען דוגמה“ קורא אותו בחזרה מ-textarea.defaultValue — התוכן שהשרת שלח בתוך התגית, שכתיבה בעורך אינה משנה.

העורך, התצוגה החיה וסרגל ה-MD

העמוד משתמש באותם רכיבים כמו עמוד העריכה, ולכן תיקון באחד מהם מגיע לשניהם. מה שהעמוד צריך אחרת עובר כאפשרות שרק הוא מפעיל, ועמודי העריכה וההעלאה לא מושפעים ממנה:

  • העורך (EditorManager ב-editor-manager.js): initEditor עם fixedEditor: true. העורך תמיד CodeMirror, בלי כפתור המעבר לעורך הפשוט, ובלי להתחשב בהעדפת העורך או לשמור אותה — ההעדפה שייכת לעמודי העריכה, ושמירתה נשלחת ל-API של משתמש מחובר. ”בחר הכל“, ”העתק“ ו“הדבק“ נשארים, ולפניהם ”בטל“ ו“בצע שוב“ — אייקון בלי טקסט, על ההיסטוריה של העורך, כמו בעמודי העריכה וההעלאה. בסוף השורה אין תווית, וההודעה הזמנית צפה שם (⌨️ עורך קוד (WebApp Editor)). אם CodeMirror נכשל, העורך הפשוט עולה, כדי שאפשר יהיה להמשיך לכתוב.

  • מימין לשמאל, חוץ מבלוקי קוד — initEditor עם textDirection: 'rtl'. העורך כולו מימין לשמאל, ורק השורות של בלוק קוד (בין גדרות של שלושה גרשיים הפוכים, כולל שורות הגדר, או מוזח) משמאל לימין, לפי מה שהמנתח של Markdown בעורך זיהה. עד אוקטובר 2026 כל שורה קיבלה את הכיוון של האות החזקה הראשונה בה, ולכן - [x] משימה, שורת היישור של טבלה (|---:|) וקישור באנגלית בתוכן העניינים יצאו משמאל לימין. המנגנון והחבילה של CodeMirror שהוא יושב בה — ⌨️ עורך קוד (WebApp Editor). עמודי העריכה וההעלאה נשארים משמאל לימין, כמו עורך קוד.

  • התצוגה החיה (LivePreviewController ב-live-preview.js), לפי מאפיינים על #livePreviewRoot:

    • data-preview-mode="markdown" — תמיד Markdown. בלעדיו המצב נקבע לפי השפה ושם הקובץ, ובעמוד בלי השדות האלה התצוגה לא הייתה זמינה.

    • data-preview-autostart — דלוקה מההתחלה. הכפתור ”Live Preview“ נשאר, ומכבה אותה.

    • data-preview-client-only — רינדור בדפדפן בלבד. בעמוד העריכה, כשהרינדור בדפדפן נכשל, הבקר פונה ל-/api/preview/live; כאן הנתיב הזה דורש התחברות, ולכן העמוד מציג שגיאה במקום לשלוח את הטקסט.

  • סרגל ה-MD (המאקרו markdown_toolbar ב-components/editor_components.html, ו-markdown-toolbar.js): markdown_toolbar(inline=True, markdown_only=True). inline מציג את הכלים כשורה פתוחה, בלי הכפתור ”MD“, והשורה נשארת במקומה ואינה עוברת לשורת העורך; ההתראות נפתחות מ“התראות ▾“ בלחיצה, ונסגרות בלחיצה שנייה, בלחיצה מחוץ לסרגל או ב-Escape. markdown_only מציג את הסרגל ומפעיל את קיצורי המקלדת שלו תמיד, בלי לבדוק שם קובץ ושפה. בלי הפרמטרים, המאקרו מוציא את אותן תגיות כמו קודם. בשורה הפתוחה המארקר מסומן 🟰 וההתראות ℹ️, ורשימת ההתראות בעברית — השם של כל סוג הוא הכותרת שההתראה מקבלת במסמך (ADMONITION_TITLES ב-admonition-icons.js), וגם ההודעה אחרי ההוספה (getTemplateLabel); השמות כתובים גם בתבנית (alert_items), ו-tests/test_markdown_editor_page.py משווה. בתפריט ”MD“ של עמודי העריכה וההעלאה האייקונים והשמות באנגלית נשארו.

  • הפריסה — כל הגובה לכתיבה ולתצוגה. אין כרטיס עוטף: ה-Split View הוא המסגרת. שורת כלי ה-MD יושבת בראש שלו (.split-toolbar), ו-”Live Preview“ בסופה, משמאל; הקיצור Ctrl+Shift+Enter עבר לטולטיפ של הכפתור. התווית של העורך מוסתרת ויזואלית (sr-only) ונשארת לקורא מסך. בטלפון (עד 767px) העורך והתצוגה מוצגים אחד בכל פעם, עם לשוניות ”עורך“ / ”תצוגה חיה“ — רק כשהתצוגה דלוקה; כמו בעמודי העריכה וההעלאה (מערכת ערכות הנושא והטוקנים החדשה, בסעיף על Split View). בעורך אין מספרי שורות ואין חיצי קיפול (gutters: false), והטקסט מתחיל במרווח קטן מהקצה — הריווח של השורה לפי הכיוון שלה (.codemirror-container--rtl .cm-line ב-codemirror-custom.css). בתצוגה, שורת המידע (שפה, גודל, זמן) מוסתרת, והסטטוס מוצג רק בשגיאה — כמו בעמודי העריכה וההעלאה, מכלל אחד ב-split-view.css (מערכת ערכות הנושא והטוקנים החדשה, בסעיף על Split View). שאר הכללים של הפריסה בבלוק ה-<style> של markdown_editor.html.

  • צ’קליסט בתצוגה החיה — לחיצה על צ’קבוקס משנה את [ ] / [x] בשורה שלו בעורך, כמו בעמוד העריכה. ההסבר ב-מערכת ערכות הנושא והטוקנים החדשה, בסעיף על Split View.

  • גלילה מסונכרנת — גלילה בעורך מביאה את התצוגה לאותו מקום בטקסט, וגלילה בתצוגה מביאה את העורך, לפי שורות ולא לפי אחוזים (live-preview-scroll-sync.js), כמו בעמודי העריכה וההעלאה. בטלפון, במעבר בין הלשוניות, אין סנכרון. ההסבר ב-מערכת ערכות הנושא והטוקנים החדשה, בסעיף על Split View.

  • הכפתור אומר מה הוא עושה. כשהתצוגה פתוחה הוא ”הסתר תצוגה“, עם עין חצויה, וכשהיא סגורה — ”Live Preview“: data-label-active / data-label-inactive ו-data-icon-active / data-icon-inactive על הכפתור, ו-syncToggleButton ב-live-preview.js. בלי aria-pressed: הכיתוב כבר אומר מה הלחיצה הבאה תעשה. בעמודי העריכה וההעלאה הכפתור לא השתנה.

  • התצוגה החיה בלי ``¶`` ובלי H5/H6. התצוגה החיה בעמודי העורך מרנדרת בוריאנט livePreview של RENDERER_VARIANTS ב-live-preview.js: בלי ליד כותרת, ו-##### / ###### נשארות כמו שנכתבו, כפסקה — הגופן של H5 ו-H6 היה קטן מטקסט רגיל. מנובמבר 2026 גם תצוגת המסמך (md_preview.html: /md/<file_id> והשיתוף ב-/share/<share_id>?view=md) לא מרנדרת H5/H6. הכלל עצמו יושב ב-md-heading-level-plugin.js (DOCUMENT_MAX_LEVEL), ושני הרנדררים מתקינים אותו; ה- נשאר בתצוגת המסמך. דפדפן הריפו וכרטיסי החיפוש בתיעוד משתמשים בוריאנט default, שלא השתנה. ומטקסט ההדגמה הוסרו שתי הכותרות האלה. עוגן מפורש (<a id> בכותרת, או בשורה שלפניה) מוחל לפי המספר הסידורי של היעד שלו, ולכן הפסקה ש-##### / ###### הופכת אליה נשארת יעד: היא מסומנת ברמה שבה נכתבה (data-md-heading-level), ו-applyExplicitAnchors ב-md-anchors.js מחפש יעדים בכותרות ובפסקאות המסומנות (ANCHOR_TARGETS). כך עוגן של כותרת כזו נוחת על הפסקה שלה, וכל עוגן אחר נשאר על הכותרת שלו. שתי טעויות קודמות: כשהחילוץ ספר את ##### ככותרת והתצוגה לא, העוגן של הכותרת שאחריה הוחל על הכותרת הבאה; וכשהחילוץ דילג עליה, עוגן שלפניה עבר לכותרת הבאה, ותגית בתוכה נשארה בטקסט.

  • מה שמתחת לעורך — בבלוק מתקפל, סגור כברירת מחדל, בעיצוב של ”קיפול“ במסמך (markdown-details מ-markdown-enhanced.css). וגלילה בעורך או בתצוגה, כשהם כבר בסוף, ממשיכה לגלול את העמוד, כמו בעמודי העריכה וההעלאה. מאוקטובר 2026 עד נובמבר 2026 היה כאן overscroll-behavior: contain, שעצר אותה, והוא הוסר לבקשת המשתמש.

הטיוטה והכפתורים

  • איפה. ב-localStorage, תחת המפתח STORAGE_KEY ב-markdown-editor.js, כאובייקט JSON אחד: code ו-savedAt. נשמרת אחרי הפסקה בהקלדה (SAVE_DELAY_MS), וכשיוצאים מהעמוד (visibilitychange ל-hidden, ו-pagehide). טיוטה שאינה בצורה הזו נחשבת כאילו אין טיוטה. הטקסט לא נשלח לשרת.

  • שחזור לפני הרינדור הראשון. הטיוטה נכתבת ל-textarea כבר כשהסקריפט רץ, לפני DOMContentLoaded, שבו נבנה הבקר של התצוגה החיה. כך הרינדור הראשון הוא של הטיוטה, ולא של ההדגמה.

  • ההדגמה אינה טיוטה. טקסט ששווה בדיוק להדגמה נמחק מהאחסון, כדי שגרסה חדשה של ההדגמה תגיע גם למי שלא כתב כלום.

  • ”ניקוי“ מבקש אישור, מרוקן את העורך ושומר טיוטה ריקה — כדי שבביקור הבא העורך יישאר ריק ולא יחזור להדגמה.

  • ”טען דוגמה“ מבקש אישור כשבעורך יש טקסט אחר, ומחזיר את ההדגמה.

  • ”הורד ‎.md“ מוריד את הטקסט שבעורך כקובץ DOWNLOAD_NAME, דרך downloadBlob (העזרים המשותפים, למטה). הכתובת של ה-Blob משתחררת אחרי DOWNLOAD_REVOKE_DELAY_MS, כמו ב-FileSaver.js, ולא מיד אחרי הלחיצה.

  • בגלישה פרטית, או כשהדפדפן חוסם אחסון, העמוד עובד ואומר שהטקסט לא יישמר. כשהשמירה נכשלת באמצע (למשל מכסה מלאה), הוא מציע להוריד את הטקסט כקובץ.

  • בשני חלונות פתוחים כל אחד שומר את הטקסט שלו, והאחרון שנשמר הוא מה שיופיע בביקור הבא.

ייצוא HTML מעוצב

”HTML מעוצב“ פותח שורה עם הערכות שאפשר לייצא בהן, ו“הצג“ או ”הורד HTML“ בונים את המסמך כקובץ HTML מעוצב שנפתח לבד. לכפתור יש רקע עדין של שלושה גוונים מהערכה שנע לאט מצד לצד, ובלי תנועה למי שביקש במכשיר פחות תנועה; ההסבר בהערה שמעל .md-editor-export-toggle ב-markdown_editor.html. כל מה שהקובץ צריך — העיצוב, הגופנים של הנוסחאות והתרשימים — בתוכו, ולכן הוא עובד גם בלי רשת; היוצא מן הכלל הוא תמונה מכתובת חיצונית, שנטענת מהשרת שלה כשהקובץ נפתח, ובלי רשת לא תוצג. כמו בכל העמוד, הטקסט לא נשלח לשרת, וזה מה שקבע את המבנה.

  • השלד בשרת, התוכן בדפדפן. השרת מגיש רק ”שלד“: העמוד המעוצב של הערכה, עם הערת HTML במקום התוכן (EDITOR_EXPORT_CONTENT_MARKER ב-services/styled_export_service.py), ובלי שום קלט של הגולש. הנתיב הוא markdown_editor_export_skeleton ב-webapp/app.py: GET /api/markdown-editor/export-skeleton/<theme_id>. הדפדפן מרנדר את המסמך, מבקש את השלד, ומכניס את המסמך במקום ההערה (webapp/static/js/markdown-editor-export.js). לייצוא הקיים של המחוברים (/export/styled/<file_id>) הסדר הפוך — הקובץ שמור בשרת, והשרת ממיר אותו ב-Python-Markdown — ולכן הוא לא מתאים לעמוד הזה.

  • הערכות. רשימה סגורה, EXPORT_THEME_IDS ב-webapp/markdown_editor.py, מתוך EXPORT_PRESETS: morass, ‏Pen Paper Coffee UI ו-Everforest Dark. כל ערך אחר בנתיב מקבל 404. הערכות האלה מופיעות גם בחלון הייצוא של המחוברים. צבעי התחביר של כל אחת נגזרים מה-tokenColors שלה: כללי Pygments לייצוא של המחוברים, וכללי highlight.js (hljs_css) לייצוא מהעורך. הבורר בעמוד נבנה מאותה רשימה (export_themes), וכל ערכה נושאת בו את כתובת השלד שלה ואת ערכת ה-Mermaid שלה, לפי בהירות הערכה. ב-Everforest Dark חלק מהקוד לא נצבע כמו ב-VS Code, למשל ההערות: כמחצית מהרשומות ב-tokenColors שלה כתובות כמחרוזת של כמה scopes מופרדים בפסיק, והמחוללים ב-services/theme_parser_service.py עוד לא מפצלים מחרוזת כזו. הרשימה של הערכה נשמרה כמו בקובץ, כדי שתיקון במחוללים יחול עליה. הפירוט בכרטיס ”מחוללי צבעי התחביר — scope עם פסיקים וכיוון ההתאמה“ בלוח ה-issues ב-CodeKeeper.

  • רינדור נפרד, לא העתקה של התצוגה החיה. MarkdownLiveRenderer.renderForExport ב-live-preview.js מרנדר באותו מנוע, בווריאנט export של RENDERER_VARIANTS (כמו livePreview, בלי שורות המקור), לתוך אלמנט שאינו בעמוד, ומחכה שכל התרשימים יצוירו. העתקה של מה שמוצג לא הייתה עובדת: כשהתצוגה כבויה מוצג בה רק טקסט הסבר, באמצע הקלדה היא באמצע רינדור, והתרשימים בה בערכה של העמוד ולא של הקובץ. בקובץ אין כפתורי העתקה של התצוגה (copyButtons: false, ובפס הכותרת של בלוק הקוד — copyButton: false ל-md-code-blocks.js), והצ’קבוקסים לא לחיצים. עוגן מפורש (<a id> בכותרת) נבדק מול התוכן עצמו ולא מול העמוד (takenIds ב-md-anchors.js): התצוגה החיה, שבאותו עמוד, כבר מחזיקה את אותם מזהים, ובדיקה מול העמוד הייתה משמיטה כל עוגן מפורש מהקובץ.

  • Mermaid בשני רינדורים יחד. mermaid.initialize משנה הגדרה גלובלית, ולכן כל הרינדורים של Mermaid — התצוגה החיה והייצוא — עוברים בתור אחד (mermaidQueue). המזהים של ה-SVG נגזרים ממונה אחד לכל המודול (mermaidPassCount); עד הייצוא הם נבנו מ-Date.now(), ושני רינדורים באותה אלפית שנייה היו מקבלים אותם מזהים. כדי שהתור לא יעכב את הרינדור החדש, רינדור של התצוגה החיה שרינדור חדש כבר החליף מפסיק לצייר את התרשימים שנשארו לו (isCurrent ל-enhance).

  • הקובץ. בלי באנר עליון — לקובץ אין שם, והכיתוב בתחתית הוא EDITOR_EXPORT_FOOTER, בלי ”Bot“. שם המסמך (<title>) הוא הכותרת הראשונה בו, ונכנס דרך document.title, כטקסט. העיצוב של הסימון של markdown-it — התראות, פס הכותרת של בלוק קוד, מארקר, צ’קליסט, בלוק קוד בעברית ותרשים — ב-webapp/templates/export/_editor_export_styles.css, שהתבנית כוללת רק כש-editor_export דלוק; הייצוא של המחוברים לא השתנה. ההתראות מקבלות את הצבעים של .alert-<קבוצה> לפי ADMONITION_TYPES. כשבמסמך יש נוסחה, השלד מתבקש עם math=1 ומקבל את ה-CSS של KaTeX עם הגופנים בתוכו (katex_inline_css ב-webapp/markdown_editor.py, מתוך md_preview.bundle.css); בלעדיו, השורש הריבועי של KaTeX מותח את כל העמוד לרוחב (ההסבר והמדידה ב-docstring של katex_inline_css). מסמך בלי נוסחה לא נושא אותו.

  • ”הצג“ ו“הורד HTML“. ”הצג“ פותח את הקובץ בטאב חדש, כמו התצוגה המקדימה בחלון הייצוא של המחוברים. הטאב נפתח מיד בלחיצה, לפני ההמתנה לרינדור ולשלד: דפדפן מתיר לפתוח חלון רק זמן קצר אחרי פעולה של המשתמש (”transient activation“ בתקן של HTML), ורינדור של מסמך עם תרשימים יכול להימשך יותר. ”הורד HTML“ מוריד אותו כ-document.html, באותו מנגנון כמו ”הורד ‎.md“. לבקשת השלד יש תקרת זמן משלה (SKELETON_TIMEOUT_MS), כי הכפתורים מושבתים כל עוד היא פתוחה.

  • אבטחה. התוכן הוא מה שהתצוגה החיה כבר מכניסה לעמוד עצמו: markdown-it רץ עם html: false, קישור javascript: נשאר טקסט, ‏KaTeX לא יוצר קישור מ-\href, ו-Mermaid רץ ב-securityLevel: 'strict'. בנוסף, בקובץ יש CSP (editor_export_meta_csp), כי הקובץ נפתח מהדיסק, בלי כותרת מהשרת: הכול חסום חוץ מסקריפט ההעתקה (לפי ה-hash שלו), העיצוב שבתוך הקובץ, הגופנים המוטמעים ותמונות. תמונות מכתובת חיצונית מותרות בכוונה — תמונה שהגולש שם במסמך היא חלק ממנו — ולכן פתיחת קובץ עם תמונה כזו פונה לשרת שלה, כמו כל עמוד HTML עם תמונה. לנתיב עצמו CSP מחמיר משלו, no-cache עם ETag, והוא כפוף למגבלת הקצב הגורפת, שלא כמו העמוד: זו פעולה, לא עמוד שסורקים נכנסים אליו. הוא תחת /api/, ש-robots.txt חוסם.

  • סקריפט ההעתקה (COPY_CODE_SCRIPT, משותף לייצוא של המחוברים) מעתיק ב-textContent: innerText נגזר מהפריסה, ומוסיף שורה ריקה סביב קטע קוד בתוך בלוק קוד בעברית. קישורי שיתוף מעוצבים (/shared/styled/<token>) שומרים את ה-HTML כמו שנוצר, עם הסקריפט של אז, ולכן ה-CSP שלהם (styled_share_csp) מתיר גם את ה-hash של הגרסאות הקודמות (PREVIOUS_COPY_CODE_SCRIPT_CSP_HASHES).

העזרים המשותפים של עמודי העורך

webapp/static/js/utils/editor-page-utils.js (window.EditorPageUtils) מחזיק את העזרים שהיו מועתקים בין עמודי העורך: sleep, waitFor, debounce, formatTime, getEditorContent, setEditorContent, storageAvailable ו-downloadBlob. הצרכנים — edit-draft.js, upload-page.js, file-form-manager.js, markdown-editor.js ו-markdown-editor-export.js — קוראים אותו כבר כשהם נטענים, ולכן כל תבנית שטוענת צרכן טוענת את editor-page-utils.js לפניו, באותה צורה (defer). tests/editor-page-utils.test.js בודק את הסדר בכל תבנית, ונכשל על צרכן שמגדיר מחדש אחד מהעזרים.

מנועי חיפוש

  • בעמוד: <title>, description, <link rel="canonical">, תגיות og: עם כתובת מלאה (_webapp_base_url), ונתונים מובנים מסוג WebApplication (schema.org).

  • ``/robots.txt`` מפנה ל-/sitemap.xml בכתובת מלאה, וחוסם רק את /api/. הסטטיקה לא חסומה: גוגל מרנדר עמוד עם ה-CSS וה-JS שלו.

  • ``/sitemap.xml`` מונה את העמודים הציבוריים שב-SITEMAP_ENDPOINTS (webapp/app.py), לפי שם ה-endpoint, בכתובת מלאה ומבורחת. עמוד ציבורי חדש נכנס למפה כשמוסיפים אותו לרשימה. שני הנתיבים פטורים ממגבלת הקצב הגורפת, כמו העמוד.

  • קישורים פנימיים לעמוד: מכרטיס ה-Markdown בעמוד הבית, ומתחת ללוח של הגלריה.

הבדיקות

  • tests/test_markdown_editor_page.py — מה שהשרת מגיש: העמוד בלי התחברות ובלי מגבלת קצב, הערכה (גם למשתמש מחובר עם ערכה מותאמת, וגם כשהערכה חסרה), טקסט ההדגמה כמו שהוא בקובץ, התגיות, robots.txt ו-sitemap.xml.

  • tests/test_markdown_editor_browser.py — בדפדפן, מול האפליקציה האמיתית: העורך והתצוגה בלי פנייה לשרת, השחזור לפני הרינדור הראשון, ”ניקוי“, ”טען דוגמה“, ההורדה, סרגל ה-MD, ונייד; ומאז התיקונים: הכיוון בעורך (מימין לשמאל חוץ מבלוקי קוד), עורך בלי מספרי שורות ובריווח מהקצה, שאין שורות של שטח מת, שההודעה הזמנית לא מזיזה את העורך, שתפריט ההתראות לא נחתך, הצ’קליסט — כולל לחיצה על תצוגה שעוד לא רונדרה מחדש — תצוגה בלי ובלי H5/H6, קו ההפרדה, הכיתוב של הכפתור, הבלוק המתקפל, שהפאנלים לא מתכווצים במסמך ריק, הדבקה שמחליפה את כל המסמך, שאין קו גרירה בין הפאנלים, ו“בטל“ / ”בצע שוב“ — הסדר בשורה, ההיסטוריה המשותפת עם Ctrl+Z, המצב המושבת, החצים בתמונת ראי, שנגיעה בהם לא מזיזה את הפוקוס, ושהכפתורים נשארים בשורה אחת ברוחב של טאבלט. והגלילה המסונכרנת: כותרת בנקודת הקריאה של צד אחד נמצאת בנקודת הקריאה של השני, בשני הכיוונים, הכותרת של כל קונטיינר בנקודת הקריאה, ההתחלה והסוף מתיישרים, כתיבה בסוף המסמך מביאה את התצוגה לסוף, הצד שגוללים בו לא נמשך אחורה, גלילה בזמן רינדור מחכה לסופו, ורינדור שרינדור חדש הקדים אותו לא שומר את התוצאות שלו (המדידה ב-tests/_scroll_sync_harness.py).

  • tests/test_edit_page_frame_browser.py — התיקונים המשותפים, בעמוד העריכה, שבו העורך משמאל לימין: נגיעה במסגרת שמימין לטקסט, ההודעה הזמנית, הגובה במסמך ריק, הדבקה שמחליפה את כל המסמך (בכפתור ”הדבק“ ובעורך הפשוט), שאין קו גרירה, ”בטל“ / ”בצע שוב“ (והסתרתם בעורך הפשוט), ושההודעה הזמנית לא מכסה את כפתור ה-MD, והגלילה המסונכרנת עם עורך משמאל לימין ועם מספרי שורות, ושבעורך הפשוט אין סנכרון — ושמה שהשתנה רק בעורך הציבורי (מספרי השורות, הכיוון, הכפתור) נשאר כאן כמו שהיה. נגיעה במסגרת נבדקת רק כאן: בעורך הציבורי אין מסגרת רחבה, כי מאז שהוסרו מספרי השורות הטקסט מתחיל 9px מהגבול (נמדד בחלון 1000x620), וכל נגיעה בטווח הזה נכנסת לעורך, כמו נגיעה ליד כל שדה טקסט.

  • tests/test_markdown_editor_export.py — מה שהשרת מגיש לייצוא: השלד בלי התחברות ובלי שום קלט, 404 לכל ערכה אחרת, ה-CSP של הנתיב ושל הקובץ, ה-ETag, ‏KaTeX רק עם math=1, שכל ערכה נגזרת מה-tokenColors שלה, שהצבעים של ההתראות זהים לאלה של התבנית, שהמחלקות שהשלד מעצב הן אלה שהמנוע מייצר, ושקישור שיתוף שנוצר עם סקריפט ההעתקה הקודם עדיין מתיר אותו.

  • tests/test_markdown_editor_export_browser.py — הייצוא בדפדפן, תחת ה-CSP האמיתי: הקובץ שירד, כשהוא נפתח לבד ובלי רשת, בכל ערכה, רכיב-רכיב; שהטקסט לא נשלח לשרת; שהייצוא עובד כשהתצוגה כבויה ועל תוכן בקיפול סגור; שניסיונות הזרקה לא מריצים כלום, וש-CSP שבקובץ חוסם סקריפט שנכנס אליו; ”הצג“; ייצוא שנכשל (הודעה לגולש, כפתורים שמשתחררים, טאב שנסגר); העתקה של בלוק קוד בעברית; עוגן מפורש שמגיע לקובץ כשהתצוגה החיה כבר מחזיקה אותו; ושני ייצואים במקביל שומרים על ערכת ה-Mermaid שלהם ועל מזהים משלהם, ורינדור שהוחלף לא מצייר.

  • tests/editor-page-utils.test.js — העזרים המשותפים, והחיווט שלהם בתבניות.

  • tests/live-preview-scroll-sync.test.js ו-tests/md-source-lines-plugin.test.js — החשבון של הגלילה המסונכרנת, וסימון שורות המקור בתצוגה.