From 8c2e65e4a28c3fedd9df6ab508091560ab0f3d00 Mon Sep 17 00:00:00 2001 From: ZDDC Date: Fri, 1 May 2026 21:09:46 -0500 Subject: [PATCH] fix(mdedit): two-line ZDDC tree display + dark-mode editor contrast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues from one session: * File tree: ZDDC-conforming filenames render as a single line even though the JS already produced two-div markup (filename-main + filename-secondary). Cause: .tree-row__label was display:flex (row-direction), so the two divs laid out side-by-side. Fix: wrap each label's text in a new .tree-row__name span styled flex-direction:column. Both file and folder code paths use the same wrapper now; non-ZDDC entries collapse to a single .filename-main line so typography stays consistent across the tree. Tested by injecting a ZDDC filename into a mock directory and asserting filename-secondary's bounding-box top is below filename-main's bottom. * Toast UI Editor was unreadable in dark mode. Toast UI ships with light-only chrome; its .toastui-editor-md-container has color #222 on a transparent bg, so when mdedit's dark theme rendered the surrounding pane in #1e1e1e the editor text fell on near-black background → effectively invisible. Fix: add CSS overrides in mdedit/css/editor.css that target the editor's load-bearing surfaces (md-container, md-preview, ww-container, ProseMirror, toolbar, mode-switch tabs, popups) and apply var(--bg) / var(--text). Toolbar icons get a filter:invert(0.85) hue-rotate to flip the sprite-baked dark glyphs. Both manual override (data-theme="dark") and OS-pref auto fallback (prefers-color-scheme) are covered. Tested by computing contrast ratios on every editor surface in dark mode — all came in at 10:1+ (well above WCAG AA's 4.5:1). Embedded snapshots refreshed to current main HEAD's dev build label. Co-Authored-By: Claude Opus 4.7 (1M context) --- mdedit/css/base.css | 16 +- mdedit/css/editor.css | 94 + mdedit/dist/mdedit.html | 6205 ++++++++++++++++++ mdedit/js/file-tree.js | 23 +- zddc/internal/apps/embedded/archive.html | 2 +- zddc/internal/apps/embedded/classifier.html | 2 +- zddc/internal/apps/embedded/index.html | 2 +- zddc/internal/apps/embedded/mdedit.html | 135 +- zddc/internal/apps/embedded/transmittal.html | 2 +- zddc/internal/apps/embedded/versions.txt | 10 +- 10 files changed, 6463 insertions(+), 28 deletions(-) create mode 100644 mdedit/dist/mdedit.html diff --git a/mdedit/css/base.css b/mdedit/css/base.css index af8f55a..2ace20e 100644 --- a/mdedit/css/base.css +++ b/mdedit/css/base.css @@ -337,13 +337,25 @@ flex: 1; min-width: 0; overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; display: flex; align-items: center; gap: 0.25rem; } +/* The text wrapper inside a tree-row label. For ZDDC-conforming files and + folders, this wraps two stacked
s (filename-main + filename-secondary) + so the row reads top-to-bottom as title + metadata — same shape the archive + tool uses for its transmittal-folder list. For non-ZDDC entries it just + contains a single line. flex column makes the two-line case work; min-width:0 + lets each line truncate independently. */ +.tree-row__name { + display: flex; + flex-direction: column; + min-width: 0; + flex: 1; + line-height: 1.25; +} + /* ── New-file modal ─────────────────────────────────────────────────────────── */ .modal-overlay { position: fixed; diff --git a/mdedit/css/editor.css b/mdedit/css/editor.css index 1168c4a..d303a95 100644 --- a/mdedit/css/editor.css +++ b/mdedit/css/editor.css @@ -23,3 +23,97 @@ .toastui-editor-main .toastui-editor-md-preview { height: 100% !important; } + +/* ── Toast UI Editor — dark-theme overrides ─────────────────────────────── + Toast UI ships with light-mode chrome and edit surfaces by default. In + mdedit's dark mode the editor's text (#222) falls onto the transparent + md-container, which inherits var(--bg) dark = #1e1e1e → effectively + black-on-black. Override the load-bearing surfaces with mdedit's tokens + so the editor harmonises with the rest of the chrome. + The selectors target both manual override (data-theme="dark") and the + OS-pref auto fallback (prefers-color-scheme + no data-theme="light"). */ + +/* Manual dark override */ +[data-theme="dark"] .toastui-editor-defaultUI, +[data-theme="dark"] .toastui-editor-md-container, +[data-theme="dark"] .toastui-editor-md-preview, +[data-theme="dark"] .toastui-editor-ww-container, +[data-theme="dark"] .toastui-editor-mode-switch, +[data-theme="dark"] .toastui-editor-main, +[data-theme="dark"] .ProseMirror { + background-color: var(--bg); + color: var(--text); +} +[data-theme="dark"] .toastui-editor-defaultUI-toolbar { + background-color: var(--bg-secondary); + border-bottom-color: var(--border); +} +[data-theme="dark"] .toastui-editor-md-splitter { + background-color: var(--border); +} +[data-theme="dark"] .toastui-editor-toolbar-icons { + /* Toast UI's icons are sprite-baked dark; invert flips them to light. */ + filter: invert(0.85) hue-rotate(180deg); +} +[data-theme="dark"] .toastui-editor-toolbar-divider { + background-color: var(--border); +} +[data-theme="dark"] .toastui-editor-mode-switch { + border-top-color: var(--border); +} +[data-theme="dark"] .toastui-editor-mode-switch .tab-item { + color: var(--text-muted); +} +[data-theme="dark"] .toastui-editor-mode-switch .tab-item.active { + color: var(--text); + background-color: var(--bg); +} +[data-theme="dark"] .toastui-editor-popup, +[data-theme="dark"] .toastui-editor-context-menu { + background-color: var(--bg-secondary); + color: var(--text); + border-color: var(--border); +} + +/* OS-pref auto fallback (matches every selector above) */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) .toastui-editor-defaultUI, + :root:not([data-theme="light"]) .toastui-editor-md-container, + :root:not([data-theme="light"]) .toastui-editor-md-preview, + :root:not([data-theme="light"]) .toastui-editor-ww-container, + :root:not([data-theme="light"]) .toastui-editor-mode-switch, + :root:not([data-theme="light"]) .toastui-editor-main, + :root:not([data-theme="light"]) .ProseMirror { + background-color: var(--bg); + color: var(--text); + } + :root:not([data-theme="light"]) .toastui-editor-defaultUI-toolbar { + background-color: var(--bg-secondary); + border-bottom-color: var(--border); + } + :root:not([data-theme="light"]) .toastui-editor-md-splitter { + background-color: var(--border); + } + :root:not([data-theme="light"]) .toastui-editor-toolbar-icons { + filter: invert(0.85) hue-rotate(180deg); + } + :root:not([data-theme="light"]) .toastui-editor-toolbar-divider { + background-color: var(--border); + } + :root:not([data-theme="light"]) .toastui-editor-mode-switch { + border-top-color: var(--border); + } + :root:not([data-theme="light"]) .toastui-editor-mode-switch .tab-item { + color: var(--text-muted); + } + :root:not([data-theme="light"]) .toastui-editor-mode-switch .tab-item.active { + color: var(--text); + background-color: var(--bg); + } + :root:not([data-theme="light"]) .toastui-editor-popup, + :root:not([data-theme="light"]) .toastui-editor-context-menu { + background-color: var(--bg-secondary); + color: var(--text); + border-color: var(--border); + } +} diff --git a/mdedit/dist/mdedit.html b/mdedit/dist/mdedit.html new file mode 100644 index 0000000..03432a0 --- /dev/null +++ b/mdedit/dist/mdedit.html @@ -0,0 +1,6205 @@ + + + + + + ZDDC Markdown + + + + + + + +
+
+
+ +
+ ZDDC Markdown + v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty +
+ +
+
+ + +
+
+ +
+
+ + +
+ +
+ + + +
+
+
+ +
+
+ + 0 folders + 0 files + 0 unsaved +
+ +
+ + + + + + +
+ + + + diff --git a/mdedit/js/file-tree.js b/mdedit/js/file-tree.js index b6097a0..916fe91 100644 --- a/mdedit/js/file-tree.js +++ b/mdedit/js/file-tree.js @@ -101,7 +101,7 @@ function renderFileTree() { const scratchLabel = document.createElement('span'); scratchLabel.className = 'tree-row__label'; - scratchLabel.innerHTML = '
📝 Scratchpad
Quick notes — no directory needed
'; + scratchLabel.innerHTML = '
📝 Scratchpad
Quick notes — no directory needed
'; scratchpadElement.appendChild(scratchLabel); const scratchActions = document.createElement('div'); @@ -169,7 +169,10 @@ function renderFileTree() { const meta = `${parsedFolder.trackingNumber} (${parsedFolder.status}) — ${parsedFolder.date}`; dirName.innerHTML = `
📁 ${escapeHtml(parsedFolder.title)}
${escapeHtml(meta)}
`; } else { - dirName.textContent = `📁 ${name}`; + // Non-ZDDC folder: still wrap in filename-main so + // typography matches the two-line entries (same font + // size + weight; just no secondary line). + dirName.innerHTML = `
📁 ${escapeHtml(name)}
`; } const dirLabel = document.createElement('span'); @@ -209,24 +212,30 @@ function renderFileTree() { const fileIcon = getFileTypeIcon(name); - let fileNameDisplay; + // Build the inner two-line text inside a tree-row__name + // wrapper (column-flex). ZDDC-conforming filenames split + // into title + meta; "Title - filename.ext" pattern uses + // the dash as the same split. Plain names get a single + // line via filename-main only — same wrapper, just no + // secondary div, so the layout stays consistent. + let fileNameInner; const parsed = zddc.parseFilename(name); if (parsed && parsed.valid) { const titleDisplay = escapeHtml(parsed.title); const metaDisplay = escapeHtml(`${parsed.trackingNumber}_${parsed.revision} (${parsed.status})`); - fileNameDisplay = `
${fileIcon} ${titleDisplay}
${metaDisplay}
`; + fileNameInner = `
${fileIcon} ${titleDisplay}
${metaDisplay}
`; } else if (name.includes(' - ')) { const dashIdx = name.lastIndexOf(' - '); const secondary = escapeHtml(name.substring(0, dashIdx)); const primary = escapeHtml(name.substring(dashIdx + 3).replace(/\.[^.]+$/, '')); - fileNameDisplay = `
${fileIcon} ${primary}
${secondary}
`; + fileNameInner = `
${fileIcon} ${primary}
${secondary}
`; } else { - fileNameDisplay = `${fileIcon} ${escapeHtml(name)}`; + fileNameInner = `
${fileIcon} ${escapeHtml(name)}
`; } const fileLabel = document.createElement('span'); fileLabel.className = 'tree-row__label'; - fileLabel.innerHTML = fileNameDisplay; + fileLabel.innerHTML = `${fileNameInner}`; const fileActions = createActionButtons(filePath, 'file'); diff --git a/zddc/internal/apps/embedded/archive.html b/zddc/internal/apps/embedded/archive.html index 43b0b5a..5bfaf91 100644 --- a/zddc/internal/apps/embedded/archive.html +++ b/zddc/internal/apps/embedded/archive.html @@ -2113,7 +2113,7 @@ td[data-field="trackingNumber"] {
ZDDC Archive - v0.0.8 + v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty
diff --git a/zddc/internal/apps/embedded/classifier.html b/zddc/internal/apps/embedded/classifier.html index 410b25d..6b56ff4 100644 --- a/zddc/internal/apps/embedded/classifier.html +++ b/zddc/internal/apps/embedded/classifier.html @@ -1376,7 +1376,7 @@ body.help-open .app-header {
ZDDC Classifier - v0.0.8 + v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty
diff --git a/zddc/internal/apps/embedded/index.html b/zddc/internal/apps/embedded/index.html index 035ac0e..7009814 100644 --- a/zddc/internal/apps/embedded/index.html +++ b/zddc/internal/apps/embedded/index.html @@ -866,7 +866,7 @@ body { ZDDC Archive - v0.0.8 + v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty
diff --git a/zddc/internal/apps/embedded/mdedit.html b/zddc/internal/apps/embedded/mdedit.html index fad6384..03432a0 100644 --- a/zddc/internal/apps/embedded/mdedit.html +++ b/zddc/internal/apps/embedded/mdedit.html @@ -1066,13 +1066,25 @@ body.help-open .app-header { flex: 1; min-width: 0; overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; display: flex; align-items: center; gap: 0.25rem; } +/* The text wrapper inside a tree-row label. For ZDDC-conforming files and + folders, this wraps two stacked
s (filename-main + filename-secondary) + so the row reads top-to-bottom as title + metadata — same shape the archive + tool uses for its transmittal-folder list. For non-ZDDC entries it just + contains a single line. flex column makes the two-line case work; min-width:0 + lets each line truncate independently. */ +.tree-row__name { + display: flex; + flex-direction: column; + min-width: 0; + flex: 1; + line-height: 1.25; +} + /* ── New-file modal ─────────────────────────────────────────────────────────── */ .modal-overlay { position: fixed; @@ -1146,6 +1158,100 @@ body.help-open .app-header { .toastui-editor-main .toastui-editor-md-preview { height: 100% !important; } + +/* ── Toast UI Editor — dark-theme overrides ─────────────────────────────── + Toast UI ships with light-mode chrome and edit surfaces by default. In + mdedit's dark mode the editor's text (#222) falls onto the transparent + md-container, which inherits var(--bg) dark = #1e1e1e → effectively + black-on-black. Override the load-bearing surfaces with mdedit's tokens + so the editor harmonises with the rest of the chrome. + The selectors target both manual override (data-theme="dark") and the + OS-pref auto fallback (prefers-color-scheme + no data-theme="light"). */ + +/* Manual dark override */ +[data-theme="dark"] .toastui-editor-defaultUI, +[data-theme="dark"] .toastui-editor-md-container, +[data-theme="dark"] .toastui-editor-md-preview, +[data-theme="dark"] .toastui-editor-ww-container, +[data-theme="dark"] .toastui-editor-mode-switch, +[data-theme="dark"] .toastui-editor-main, +[data-theme="dark"] .ProseMirror { + background-color: var(--bg); + color: var(--text); +} +[data-theme="dark"] .toastui-editor-defaultUI-toolbar { + background-color: var(--bg-secondary); + border-bottom-color: var(--border); +} +[data-theme="dark"] .toastui-editor-md-splitter { + background-color: var(--border); +} +[data-theme="dark"] .toastui-editor-toolbar-icons { + /* Toast UI's icons are sprite-baked dark; invert flips them to light. */ + filter: invert(0.85) hue-rotate(180deg); +} +[data-theme="dark"] .toastui-editor-toolbar-divider { + background-color: var(--border); +} +[data-theme="dark"] .toastui-editor-mode-switch { + border-top-color: var(--border); +} +[data-theme="dark"] .toastui-editor-mode-switch .tab-item { + color: var(--text-muted); +} +[data-theme="dark"] .toastui-editor-mode-switch .tab-item.active { + color: var(--text); + background-color: var(--bg); +} +[data-theme="dark"] .toastui-editor-popup, +[data-theme="dark"] .toastui-editor-context-menu { + background-color: var(--bg-secondary); + color: var(--text); + border-color: var(--border); +} + +/* OS-pref auto fallback (matches every selector above) */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) .toastui-editor-defaultUI, + :root:not([data-theme="light"]) .toastui-editor-md-container, + :root:not([data-theme="light"]) .toastui-editor-md-preview, + :root:not([data-theme="light"]) .toastui-editor-ww-container, + :root:not([data-theme="light"]) .toastui-editor-mode-switch, + :root:not([data-theme="light"]) .toastui-editor-main, + :root:not([data-theme="light"]) .ProseMirror { + background-color: var(--bg); + color: var(--text); + } + :root:not([data-theme="light"]) .toastui-editor-defaultUI-toolbar { + background-color: var(--bg-secondary); + border-bottom-color: var(--border); + } + :root:not([data-theme="light"]) .toastui-editor-md-splitter { + background-color: var(--border); + } + :root:not([data-theme="light"]) .toastui-editor-toolbar-icons { + filter: invert(0.85) hue-rotate(180deg); + } + :root:not([data-theme="light"]) .toastui-editor-toolbar-divider { + background-color: var(--border); + } + :root:not([data-theme="light"]) .toastui-editor-mode-switch { + border-top-color: var(--border); + } + :root:not([data-theme="light"]) .toastui-editor-mode-switch .tab-item { + color: var(--text-muted); + } + :root:not([data-theme="light"]) .toastui-editor-mode-switch .tab-item.active { + color: var(--text); + background-color: var(--bg); + } + :root:not([data-theme="light"]) .toastui-editor-popup, + :root:not([data-theme="light"]) .toastui-editor-context-menu { + background-color: var(--bg-secondary); + color: var(--text); + border-color: var(--border); + } +} /* Table of Contents styles */ .toc-pane { @@ -1668,7 +1774,7 @@ body.help-open .app-header {
ZDDC Markdown - v0.0.8 + v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty
@@ -4332,7 +4438,7 @@ function renderFileTree() { const scratchLabel = document.createElement('span'); scratchLabel.className = 'tree-row__label'; - scratchLabel.innerHTML = '
📝 Scratchpad
Quick notes — no directory needed
'; + scratchLabel.innerHTML = '
📝 Scratchpad
Quick notes — no directory needed
'; scratchpadElement.appendChild(scratchLabel); const scratchActions = document.createElement('div'); @@ -4400,7 +4506,10 @@ function renderFileTree() { const meta = `${parsedFolder.trackingNumber} (${parsedFolder.status}) — ${parsedFolder.date}`; dirName.innerHTML = `
📁 ${escapeHtml(parsedFolder.title)}
${escapeHtml(meta)}
`; } else { - dirName.textContent = `📁 ${name}`; + // Non-ZDDC folder: still wrap in filename-main so + // typography matches the two-line entries (same font + // size + weight; just no secondary line). + dirName.innerHTML = `
📁 ${escapeHtml(name)}
`; } const dirLabel = document.createElement('span'); @@ -4440,24 +4549,30 @@ function renderFileTree() { const fileIcon = getFileTypeIcon(name); - let fileNameDisplay; + // Build the inner two-line text inside a tree-row__name + // wrapper (column-flex). ZDDC-conforming filenames split + // into title + meta; "Title - filename.ext" pattern uses + // the dash as the same split. Plain names get a single + // line via filename-main only — same wrapper, just no + // secondary div, so the layout stays consistent. + let fileNameInner; const parsed = zddc.parseFilename(name); if (parsed && parsed.valid) { const titleDisplay = escapeHtml(parsed.title); const metaDisplay = escapeHtml(`${parsed.trackingNumber}_${parsed.revision} (${parsed.status})`); - fileNameDisplay = `
${fileIcon} ${titleDisplay}
${metaDisplay}
`; + fileNameInner = `
${fileIcon} ${titleDisplay}
${metaDisplay}
`; } else if (name.includes(' - ')) { const dashIdx = name.lastIndexOf(' - '); const secondary = escapeHtml(name.substring(0, dashIdx)); const primary = escapeHtml(name.substring(dashIdx + 3).replace(/\.[^.]+$/, '')); - fileNameDisplay = `
${fileIcon} ${primary}
${secondary}
`; + fileNameInner = `
${fileIcon} ${primary}
${secondary}
`; } else { - fileNameDisplay = `${fileIcon} ${escapeHtml(name)}`; + fileNameInner = `
${fileIcon} ${escapeHtml(name)}
`; } const fileLabel = document.createElement('span'); fileLabel.className = 'tree-row__label'; - fileLabel.innerHTML = fileNameDisplay; + fileLabel.innerHTML = `${fileNameInner}`; const fileActions = createActionButtons(filePath, 'file'); diff --git a/zddc/internal/apps/embedded/transmittal.html b/zddc/internal/apps/embedded/transmittal.html index f3c41cb..4118210 100644 --- a/zddc/internal/apps/embedded/transmittal.html +++ b/zddc/internal/apps/embedded/transmittal.html @@ -2210,7 +2210,7 @@ dialog.modal--narrow {
ZDDC Transmittal - v0.0.8 + v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty
diff --git a/zddc/internal/apps/embedded/versions.txt b/zddc/internal/apps/embedded/versions.txt index 0220293..141e567 100644 --- a/zddc/internal/apps/embedded/versions.txt +++ b/zddc/internal/apps/embedded/versions.txt @@ -1,6 +1,6 @@ # Generated by build.sh — do not edit. One = per line. -archive=v0.0.8 -transmittal=v0.0.8 -classifier=v0.0.8 -mdedit=v0.0.8 -landing=v0.0.8 +archive=v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty +transmittal=v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty +classifier=v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty +mdedit=v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty +landing=v0.0.9-alpha · 2026-05-02 02:07:03 · 17b0a4d-dirty