@helping-ai-workflow/md2doc 3.12.0 → 3.13.0

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.
package/lib/md2doc.js CHANGED
@@ -23,6 +23,8 @@ const path = require('path');
23
23
  const drawio = require('./drawio.js');
24
24
  const { applyReaderTheme } = require('./theme/tokens');
25
25
  const { highlightCode } = require('./highlight');
26
+ const waveCodec = require('./editor/wave-codec.js');
27
+ const waveOverflow = require('./wave-overflow.js');
26
28
 
27
29
  // How long to let client-side WaveDrom / Mermaid scripts render before we
28
30
  // snapshot the DOM (PDF print, or --bake-svg inert-SVG bake).
@@ -1004,6 +1006,7 @@ ${itemsHtml}
1004
1006
  renderer.br = function() { return '<br data-hard-break="1">'; };
1005
1007
  }
1006
1008
 
1009
+ let waveBlockCount = 0;
1007
1010
  renderer.code = function(token) {
1008
1011
  // token is either a string (old API) or {text, lang} object (new API)
1009
1012
  const lang = (typeof token === 'object') ? (token.lang || '') : (arguments[1] || '');
@@ -1011,6 +1014,29 @@ ${itemsHtml}
1011
1014
 
1012
1015
  if (lang === 'wavedrom') {
1013
1016
  usesWaveDrom = true;
1017
+ waveBlockCount++;
1018
+ // CLI-only: the CLI passes its source path as opts.warnWaveOverflow;
1019
+ // library and --edit renders leave it unset and stay silent.
1020
+ // One line per diagram, never one per label (final review M1): the
1021
+ // per-label lines of one diagram each named their own scale, and
1022
+ // following the first ("set to 2") left the next one ("to 4") still
1023
+ // overflowing. The line names the count, the label that needs the
1024
+ // most, and the one scale that fits them all (suggestScale).
1025
+ if (opts.warnWaveOverflow) {
1026
+ try {
1027
+ const parsed = waveCodec.parseSource(code);
1028
+ const list = parsed.ok ? waveOverflow.overflowOf(parsed.doc) : [];
1029
+ if (list.length > 0) {
1030
+ let worst = list[0];
1031
+ for (const o of list) if (o.needScale > worst.needScale) worst = o;
1032
+ const named = '"' + worst.label + '" in ' + worst.name;
1033
+ const what = list.length === 1
1034
+ ? '1 data label is wider than its segment (' + named + ')'
1035
+ : list.length + ' data labels are wider than their segments (worst: ' + named + ')';
1036
+ console.error(`md2doc: ${opts.warnWaveOverflow}: wavedrom #${waveBlockCount}: ${what}; set config.hscale to ${waveOverflow.suggestScale(list)} (estimated)`);
1037
+ }
1038
+ } catch (e) { /* a warning must never break a render */ }
1039
+ }
1014
1040
  return `\n<script type="WaveDrom">\n${code}\n</script>\n`;
1015
1041
  }
1016
1042
  if (lang === 'mermaid') {
@@ -1787,488 +1813,341 @@ ${edUlMarkerCss}${edOlCounterCss}
1787
1813
  position: fixed; inset: 0; z-index: 998; background: rgba(0,0,0,.45);
1788
1814
  display: flex; align-items: center; justify-content: center;
1789
1815
  }
1790
- /* v3.6.0 Task 13: width cap raised from 1200px to 1920px — MEASURED
1791
- (createEditorServer, four real viewports): at 1200px the cap itself
1792
- was the binding constraint at 1280/1600/1920 alike (94vw there is
1793
- 1203/1504/1805px, all above the old 1200px cap), so every screen from a
1794
- 1280 laptop up to a 1920 monitor got the IDENTICAL canvas width — the
1795
- exact complaint that opened this version ("a bigger monitor buys the
1796
- user nothing"). 1920px is chosen so 94vw is the actual governor at
1797
- every one of those four measured sizes (94% of 1920 is 1804.8, still
1798
- under the new cap) — this number is a ceiling for a screen wider than
1799
- that, not a value that binds within the measured range. At 800x600,
1800
- 94vw (752px) was already narrower than either cap, so that viewport is
1801
- unchanged by this. See task-13-report.md for the re-measured
1802
- toolbar/canvas table. */
1816
+ /* Wave redesign Task 6b (spec section 4.1): one column, top to bottom:
1817
+ title bar, toolbar, the notice card, the stage, the hint bar. The side
1818
+ rail is gone and the stage takes the whole width. The width cap of
1819
+ 1920px is v3.6.0 Task 13's: below it, 94vw governs at every measured
1820
+ screen size. position: relative because the toolbar's popovers are
1821
+ placed against the panel. */
1803
1822
  .ed-wave-panel {
1804
- background: #fff; border-radius: 6px; width: min(1920px, 94vw);
1823
+ position: relative; background: #fff; border-radius: 6px; width: min(1920px, 94vw);
1805
1824
  height: min(760px, 90vh); display: flex; flex-direction: column;
1806
1825
  box-shadow: 0 6px 28px rgba(0,0,0,.35); overflow: hidden;
1807
1826
  }
1808
1827
  .ed-wave-head {
1809
- display: flex; align-items: center; justify-content: space-between;
1810
- padding: 8px 12px; border-bottom: 1px solid #e2e2e2; font-weight: bold;
1811
- }
1828
+ display: flex; align-items: center; gap: 12px;
1829
+ padding: 8px 12px; border-bottom: 1px solid #e2e2e2;
1830
+ }
1831
+ .ed-wave-title { font-weight: bold; }
1832
+ /* The FILE's save state (spec section 4.8), the same two states and
1833
+ colours as the edit toolbar's own status slot. */
1834
+ .ed-wave-save { margin-left: auto; font-size: 12px; color: var(--md-ed-ok); }
1835
+ .ed-wave-save[data-dirty="1"] { color: var(--md-ed-dirty); }
1812
1836
  .ed-wave-close {
1813
- border: 1px solid #bbb; border-radius: 4px; background: #f6f6f6;
1814
- padding: 3px 12px; cursor: pointer;
1815
- }
1816
- /* Which way out keeps the work and which throws it away. Escape is a modal's
1817
- universal close key and here it discards a whole session, so the header
1818
- says so next to the button that does not. Normal weight against the bar's
1819
- bold heading: it is a caption, not a warning the user has to clear. */
1820
- .ed-wave-escape-hint {
1821
- margin-left: auto; margin-right: 12px; font-weight: normal;
1822
- font-size: 11px; color: #7a4b00;
1837
+ border: 0; border-radius: 4px; background: transparent; color: #555;
1838
+ width: 28px; height: 28px; font-size: 15px; line-height: 1; cursor: pointer;
1823
1839
  }
1840
+ .ed-wave-close:hover { background: var(--md-ed-hover); }
1841
+ /* Spec section 4.2: one row; groups are divided by a thin rule and 完成
1842
+ sits on the right. Buttons are quiet until hovered or switched on. */
1824
1843
  .ed-wave-toolbar {
1825
- display: flex; flex-wrap: wrap; gap: 12px; align-items: center;
1826
- padding: 8px 12px; border-bottom: 1px solid #e2e2e2; background: #fafafa;
1827
- }
1828
- /* v3.6.0 Task 12: the eight captioned sections themselves. .ed-wave-tools
1829
- is the row that holds them (the SAME element as .ed-wave-toolbar
1830
- above -- that class still owns the bar's padding/border/background,
1831
- this one owns only the group row's own gap). Each .ed-wave-tool-group
1832
- is a bordered chip so eight adjacent sections read as eight things, not
1833
- one longer wall of buttons; the caption sits to its buttons' LEFT (see
1834
- wave-panels.js's toolGroup() for why not above). */
1835
- .ed-wave-tools { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
1836
- .ed-wave-tool-group {
1837
- display: flex; align-items: center; gap: 4px;
1838
- padding: 2px 6px; border: 1px solid #ececec; border-radius: 6px;
1839
- }
1840
- .ed-wave-tool-caption {
1841
- font-size: 10px; color: #8a8a8a; letter-spacing: .5px;
1842
- white-space: nowrap; margin-right: 2px;
1843
- }
1844
- .ed-wave-brushes, .ed-wave-meta {
1845
1844
  display: flex; flex-wrap: wrap; gap: 4px; align-items: center;
1845
+ padding: 6px 12px; border-bottom: 1px solid #e2e2e2; background: #fafafa;
1846
1846
  }
1847
- /* v3.6.0 Task 10: widened from a 28x28 bare-character square to a small
1848
- column (glyph on top, the character kept as a caption underneath) so
1849
- the 22-brush palette says what each brush paints instead of relying on
1850
- the letter alone -- nine of those 22 (P, N, u, d, h, l, 2, 6-9) have no
1851
- obvious on-canvas picture from the character by itself. */
1852
- /* '.ed-wave-brushes .ed-wave-brush', not the bare class: '.ed-wave-toolbar
1853
- button' below outranks a single class on specificity (class+type beats
1854
- class alone) and would otherwise win the padding fight and squeeze the
1855
- 24px-wide icon into a content box narrower than it is. */
1856
- .ed-wave-brushes .ed-wave-brush {
1857
- width: 34px; height: 40px; border: 1px solid #c4c4c4; border-radius: 4px;
1858
- background: #fff; font-family: monospace; cursor: pointer;
1859
- display: flex; flex-direction: column; align-items: center;
1860
- justify-content: center; gap: 1px; padding: 2px 0; box-sizing: border-box;
1861
- }
1862
- .ed-wave-brush.is-on { background: #1a73e8; color: #fff; border-color: #1a73e8; }
1863
- .ed-wave-brush-char { font-size: 11px; line-height: 1; }
1864
- /* The glyph itself. Its shapes are drawn by wave-draw.js's levelIcon,
1865
- which reuses renderCanvas's own drawing functions (bandOf, brickPath,
1866
- the bus/clock/gap helpers) -- never a second hand-drawn set, so a
1867
- change to how a brush paints on the real canvas cannot leave the
1868
- toolbar showing the old picture. */
1869
- .ed-wave-level-icon { display: block; overflow: visible; }
1870
1847
  .ed-wave-toolbar button {
1871
- border: 1px solid #c4c4c4; border-radius: 4px; background: #fff;
1872
- padding: 3px 8px; font-size: 12px; cursor: pointer;
1873
- }
1874
- .ed-wave-toolbar button:disabled { opacity: .45; cursor: default; }
1875
- /* v3.5.0 Task 7: the 關聯線 arm button, armed or mid-drag. Same on/off
1876
- treatment as .ed-wave-brush.is-on above — a picked tool reads the same
1877
- way whichever toolbar it lives in. */
1878
- .ed-wave-edge-arm.is-on { background: #1a73e8; color: #fff; border-color: #1a73e8; }
1879
- /* v3.6.0 Task 13: the fixed 110px input width that used to live here moved
1880
- into .ed-wave-section-body input below — every .ed-wave-metafield
1881
- now lives inside a right-hand panel (關聯線/文件/Lane), not the toolbar,
1882
- and a panel field should fill the ~340px column rather than sit at a
1883
- width chosen for the old horizontal toolbar row. */
1884
- .ed-wave-metafield { display: flex; align-items: center; gap: 4px; font-size: 12px; }
1885
- /* v3.6.0 Task 13: the 關聯線 panel's own content, stacked vertically now
1886
- that it lives in a narrow side column rather than a wide flex-wrap
1887
- toolbar row (the old 'flex: 1 1 100%' here was what made it claim a
1888
- full row of THAT row; a panel body has no siblings to share a row
1889
- with). */
1890
- .ed-wave-edgebar { display: flex; flex-direction: column; gap: 4px; }
1891
- /* The "this drawing is not showing you everything" notice. period / phase /
1892
- config.hscale all change what the ENGINE paints and none of them is
1893
- modelled by the hand drawing, so a document carrying one says so instead
1894
- of drawing a confident wrong picture. Full row, so it cannot be mistaken
1895
- for another toolbar control. */
1896
- .ed-wave-unmodelled {
1897
- flex: 1 1 100%; font-size: 12px; color: #7a4b00; background: #fff6e0;
1898
- border: 1px solid #e8c987; border-radius: 4px; padding: 4px 8px;
1899
- }
1900
- /* The stronger of the two notices: not "this drawing is incomplete" but
1901
- "this block has nothing this editor can edit" (a reg: / assign: block).
1902
- Same shape as its neighbour so the two read as one family, one step
1903
- louder because the information gap is bigger. */
1904
- .ed-wave-nolanes { color: #7a1f00; background: #ffe9e0; border-color: #e8a987; }
1905
- /* v3.6.0 Task 13 fix round 1 (Critical 2): position: relative added.
1906
- .ed-wave-side[data-expanded] below is position: absolute, and neither
1907
- .ed-wave-panel nor (until this line) this element had a position of
1908
- their own — so its containing block was .ed-wave-overlay
1909
- (position: fixed; inset: 0), the WHOLE viewport, not the dialog.
1910
- .ed-wave-panel's own overflow: hidden did not clip it either: clipping
1911
- follows the containing-block chain, not DOM nesting, so the rail
1912
- rendered against viewport corners at a narrow window and covered
1913
- .ed-wave-head's 關閉 button — the only discoverable way to close the
1914
- dialog (a hit-test at 關閉's own rectangle returned the edge panel's
1915
- delete button instead; Escape still worked but was not discoverable).
1916
- Anchoring the rail to THIS element rather than to .ed-wave-panel itself
1917
- is deliberate, not incidental: .ed-wave-body sits below .ed-wave-head
1918
- and above .ed-wave-status, so top: 0 / bottom: 0 on the rail now lands
1919
- at body's own top/bottom — never over the header (關閉) or the status
1920
- line — without this rule having to know either one's height. */
1921
- .ed-wave-body { display: flex; flex: 1 1 auto; min-height: 0; position: relative; }
1922
- /* v3.6.0 Task 13: the old comment here hand-computed the canvas's leftover
1923
- width from the (now gone) .ed-wave-preview column's own flex-basis;
1924
- that arithmetic no longer applies — the third column is .ed-wave-side
1925
- below, which collapses to a 28px rail under 1100px specifically so the
1926
- canvas is not squeezed the way a fixed 160px minimum used to squeeze it
1927
- at 800x600 (MEASURED before this task: canvas width there was only
1928
- 282px). Re-measured after this task at four real viewports; see
1929
- task-13-report.md rather than a formula here — a number derived by hand
1930
- drifts the moment any sibling column's own width changes, which is
1931
- exactly what this task did. */
1932
- .ed-wave-lanes {
1933
- flex: 0 0 200px; border-right: 1px solid #e2e2e2; overflow: auto;
1934
- padding: 8px 0 8px 0;
1935
- }
1936
- /* The rows are absolutely positioned at index * laneHeight so they line up
1937
- with the drawing beside them cell for cell. A group title is NOT a row —
1938
- measured against the pinned engine in Task 2 — so it is drawn as a label in
1939
- the rail spanning its lanes' rows instead, which is the shape the engine
1940
- itself gives it (horizontal space in the name column, no vertical space). */
1941
- .ed-wave-lane-rows { position: relative; }
1942
- /* v3.6.0 Task 14: a row is a drag handle, a name field and (on a {}
1943
- spacer) a badge. It used to be six buttons plus the field, and the six
1944
- did not fit: flexed into what was left of 200px the field measured
1945
- 45px on the most crowded row, and a browser given a 24px box and a
1946
- three-character Chinese caption lays that caption out VERTICALLY — the
1947
- editor shipped columns of stacked glyphs where labels were meant.
1948
- Those six are toolbar buttons now (the 訊號 / 群組 sections), acting
1949
- on the one selection. */
1950
- .ed-wave-lane-row {
1951
- position: absolute; left: 0; right: 0; display: flex; align-items: center;
1952
- gap: 6px; padding-right: 6px; box-sizing: border-box;
1953
- }
1954
- .ed-wave-lane-row.is-selected { background: #eaf2fe; }
1955
- .ed-wave-lane-handle {
1956
- flex: 0 0 auto; cursor: move; color: #aaa; user-select: none; font-size: 13px;
1957
- }
1958
- .ed-wave-lane-handle:active { cursor: move; }
1959
- /* min-width, not just flex-grow: min-width is what a flex item will not
1960
- shrink past, and shrinking past it is the vertical-text failure above.
1961
- MEASURED after this change (real page, 800x600, the journey suite's own
1962
- wave fixture): rail 201px, handle 9.5px, badge 36px, and the field comes
1963
- out 178.5px ungrouped, 162.5px one group deep, 136.5px on the {} spacer
1964
- row that also carries the badge — writing-mode horizontal-tb on all six.
1965
- So 120 is slack at every depth this fixture reaches, NOT a computed
1966
- exact fit. Deeper nesting spends 16px a level and would eventually hit
1967
- the floor; there the field stops shrinking and .ed-wave-lanes scrolls,
1968
- which is the trade this rule chooses on purpose. */
1969
- .ed-wave-lane-name {
1970
- flex: 1 1 auto; min-width: 120px; font-size: 12px; padding: 2px 4px;
1971
- }
1972
- .ed-wave-lane-badge { flex: 0 0 auto; font-size: 10px; color: #888; }
1973
- .ed-wave-group, .ed-wave-group-input {
1974
- position: absolute; width: 14px; padding: 0; font-size: 11px;
1975
- writing-mode: vertical-rl; text-orientation: mixed; overflow: hidden;
1976
- border: 1px solid #d8d8d8; border-radius: 3px; background: #f2f2f2;
1977
- color: #555; cursor: pointer; box-sizing: border-box;
1978
- }
1979
- .ed-wave-group-input { width: 16px; height: 90px; cursor: text; }
1980
- /* position: relative so the data-label field below (v3.5.0 Task 11) has
1981
- an offsetParent to measure from: wave-ui.js reads canvas.offsetLeft /
1982
- offsetTop against THIS element, not a hard-coded copy of the padding
1983
- number just below, so the two cannot drift apart if that padding ever
1984
- changes. */
1985
- .ed-wave-canvas-wrap { flex: 1 1 auto; overflow: auto; padding: 8px; position: relative; }
1986
- .ed-wave-canvas { display: block; }
1987
- /* v3.5.0 Task 11: the bus data-label field, positioned by wave-ui.js's
1988
- openDataEdit() from geometry.cellRect() — left/top/width/height are all
1989
- set inline per cell, so only the visual skin lives here. Sized to sit
1990
- exactly over the box it labels, text centred to match .ed-wave-buslabel
1991
- beside it in the drawing underneath, box-sizing so the border does not
1992
- push the field past the cell it is meant to cover. */
1993
- .ed-wave-data-input {
1994
- position: absolute; z-index: 1; box-sizing: border-box; text-align: center;
1995
- font-size: 11px; padding: 0 2px; border: 1px solid #1a73e8; border-radius: 2px;
1996
- background: #fff; color: #222;
1997
- }
1998
- .ed-wave-grid { stroke: #ececec; stroke-width: 1; }
1999
- .ed-wave-line, .ed-wave-edge { stroke: #111; stroke-width: 2; fill: none; }
2000
- /* v3.6.0 Task 10: the toolbar glyph's plain-level backbone (0/1/h/l/z/u/d
2001
- only -- bus/clock/x/gap glyphs draw their own shape with their own
2002
- fixed canvas colour instead, further down). Declared after the rule
2003
- above on purpose: both are single-class selectors, so source order is
2004
- what lets currentColor win over the fixed #111 for this one case,
2005
- turning the icon white together with the rest of the button's text
2006
- under .ed-wave-brush.is-on. */
1848
+ border: 1px solid transparent; border-radius: 4px; background: transparent;
1849
+ color: #333; padding: 4px 8px; font-size: 13px; min-height: 32px; cursor: pointer;
1850
+ }
1851
+ .ed-wave-toolbar button:hover { background: var(--md-ed-hover); }
1852
+ .ed-wave-toolbar button:disabled { opacity: .45; cursor: default; background: transparent; }
1853
+ .ed-wave-sep { width: 1px; align-self: stretch; margin: 4px 4px; background: #e2e2e2; }
1854
+ .ed-wave-toolbar .ed-wave-done {
1855
+ margin-left: auto; background: #1a73e8; color: #fff; border-color: #1a73e8; padding: 4px 14px;
1856
+ }
1857
+ .ed-wave-toolbar .ed-wave-done:hover { background: #1a73e8; }
1858
+ /* A brush: the level's glyph with its keyboard character in the lower
1859
+ right corner (spec section 4.2). The glyph is wave-draw.js levelIcon,
1860
+ built from the same brick helpers that model the engine's bricks. The
1861
+ selector names the button type so it outranks the generic toolbar
1862
+ button rule above. */
1863
+ button.ed-wave-brush {
1864
+ position: relative; width: 38px; height: 34px; padding: 2px; box-sizing: border-box;
1865
+ border: 1px solid #c4c4c4; border-radius: 4px; background: #fff; font-family: monospace;
1866
+ display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
1867
+ }
1868
+ .ed-wave-brush-char {
1869
+ position: absolute; right: 2px; bottom: 1px; font-size: 9px; line-height: 1; color: #555;
1870
+ }
1871
+ .ed-wave-toolbar button.ed-wave-brush.is-on, .ed-wave-pop button.ed-wave-brush.is-on,
1872
+ .ed-wave-toolbar .ed-wave-brush-more.is-on, .ed-wave-toolbar .ed-wave-edge-arm.is-on {
1873
+ background: #1a73e8; color: #fff; border-color: #1a73e8;
1874
+ }
1875
+ .ed-wave-brush.is-on .ed-wave-brush-char { color: #fff; }
1876
+ .ed-wave-level-icon { display: block; overflow: visible; }
1877
+ /* The glyph's shapes: the plain-level backbone follows the button's text
1878
+ colour (so a picked brush turns white with its caption); bus, x, clock
1879
+ and gap glyphs carry their own canvas colours. */
1880
+ .ed-wave-line { stroke: #111; stroke-width: 2; fill: none; }
2007
1881
  .ed-wave-level-icon-path { stroke: currentColor; }
2008
1882
  .ed-wave-weak { stroke-dasharray: 4 3; }
2009
- /* v3.5.0 Task 10: the P/N explicit-edge arrowhead — see wave-ui.js's
2010
- drawLane isClock branch for the measurement behind its shape and
2011
- placement. Filled, no stroke, matching the line colour above. */
2012
1883
  .ed-wave-clock-arrow { fill: #111; stroke: none; }
2013
1884
  .ed-wave-x { fill: #d9d9d9; stroke: #111; stroke-width: 2; }
2014
1885
  .ed-wave-bus { fill: #f5e9c8; stroke: #111; stroke-width: 2; }
2015
1886
  .ed-wave-bus-3 { fill: #d8ead3; }
2016
1887
  .ed-wave-bus-4 { fill: #d6e4f7; }
2017
1888
  .ed-wave-bus-5 { fill: #f7d6e4; }
2018
- /* v3.5.0 Task 11 fix round 2: this text, not the box under it, is the
2019
- click target that opens the data-label field (wave-ui.js's onCanvasDown
2020
- gates on ev.target.classList.contains('ed-wave-buslabel') — pressing
2021
- anywhere else in the bus box still paints). pointer-events:
2022
- visiblePainted is the SVG initial value already, so this changes
2023
- nothing on its own; it is written out explicitly because that default
2024
- is now load-bearing for a real gesture, not incidental — a future
2025
- blanket pointer-events: none added upstream of this rule would
2026
- silently make the label unclickable again, and this line is what a
2027
- reviewer would have to notice and remove. */
2028
- .ed-wave-buslabel { font-size: 11px; fill: #222; pointer-events: visiblePainted; cursor: text; }
2029
- /* v3.5.0 Task 11 fix round 3: the hit target for an UNLABELLED bus run —
2030
- drawLane draws this circle in place of the text when the run has no
2031
- label, sharing the ed-wave-buslabel class above (same pointer-events,
2032
- same cursor: text) so onCanvasDown's hit test needed no change. Kept
2033
- visually quiet on purpose: dim and small, so a diagram full of
2034
- unlabelled buses reads as buses first and marks second, not as a field
2035
- of dots. Geometry (position, radius) is inline per cell, sized against
2036
- that cell's own box in drawLane so it always stays a small fraction of
2037
- it — this rule is skin only. */
2038
- .ed-wave-buslabel-empty { fill: #666; opacity: .35; }
2039
- /* v3.5.0 Task 11 fix round 4: this rect's job is to SHOW what cycle range
2040
- is selected, never to be pressed. pointer-events: none, so it can never
2041
- swallow a press meant for whatever it happens to be sitting over —
2042
- reviewer-measured, a bus label whose run is one cycle wide can sit
2043
- right up against this rect's own right edge (stroke included), and
2044
- without this a second press on that same label landed on the
2045
- selection's stroke instead of the label text underneath it, and the
2046
- data-label field never opened. Same principle as .ed-wave-cursor two
2047
- rules below, applied to the OTHER highlight this drawing draws. */
2048
- .ed-wave-selection {
2049
- fill: rgba(26,115,232,.18); stroke: #1a73e8; stroke-width: 1;
2050
- pointer-events: none;
2051
- }
2052
- /* The KEYBOARD's own cell, and deliberately not the same mark as the
2053
- selection: the selection is the RANGE a cycle operation acts on, the
2054
- cursor is the ONE cell the next keystroke paints, and after Shift+left /
2055
- right both are on screen at once. pointer-events: none so the rect can
2056
- never swallow the press the drag underneath it is made of. */
2057
- .ed-wave-cursor {
2058
- fill: none; stroke: #d93025; stroke-width: 2; pointer-events: none;
2059
- }
2060
- /* The drawing is in the Tab cycle, so it has to SAY when it holds the
2061
- keyboard: a cell cursor alone leaves a user who just Tabbed onto it with
2062
- nothing that distinguishes "the drawing has the keyboard" from "the
2063
- drawing is where I left a cursor before I walked away". */
1889
+ .ed-wave-gap { stroke: #111; stroke-width: 2; fill: none; }
1890
+ /* The one popover the toolbar has open at a time (wave-ui.js places it
1891
+ under its button): 更多's grouped brush grid, the 訊號 and 匯出 menus. */
1892
+ .ed-wave-pop {
1893
+ position: absolute; z-index: 4; background: #fff; border: 1px solid #e2e2e2;
1894
+ border-radius: 6px; box-shadow: 0 4px 16px var(--md-ed-shadow); padding: 6px;
1895
+ }
1896
+ .ed-wave-menu { display: flex; flex-direction: column; min-width: 140px; }
1897
+ .ed-wave-menu-item {
1898
+ border: 0; background: transparent; color: #333; text-align: left;
1899
+ padding: 6px 10px; font-size: 13px; border-radius: 4px; cursor: pointer;
1900
+ }
1901
+ .ed-wave-menu-item:hover, .ed-wave-menu-item:focus { background: var(--md-ed-hover); }
1902
+ /* Task 8: a menu item says the key its action also has (the grip menu's
1903
+ F2, Ctrl+D, Alt+arrows, Del) on its right; 刪除 is red. */
1904
+ .ed-wave-menu-item { display: flex; align-items: center; gap: 16px; }
1905
+ .ed-wave-menu-label { flex: 1 1 auto; }
1906
+ .ed-wave-menu-key { flex: 0 0 auto; font-size: 11px; color: #8a8a8a; }
1907
+ .ed-wave-menu-item.is-danger { color: var(--md-ed-err); }
1908
+ /* Task 8: the grip menu's 週期與相位 panel, period and phase of one lane. */
1909
+ .ed-wave-period-pop { display: flex; flex-direction: column; gap: 6px; }
1910
+ .ed-wave-period-row { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #333; }
1911
+ .ed-wave-period-cap { flex: 0 0 84px; }
1912
+ .ed-wave-period-input {
1913
+ width: 72px; box-sizing: border-box; padding: 3px 6px; font-size: 12px;
1914
+ border: 1px solid #e2e2e2; border-radius: 4px; background: #fff; color: #222;
1915
+ }
1916
+ /* Task 10 (spec section 4.6): the settings popover, the whole diagram.
1917
+ Title and cycle width on top, the overflow card under them, the rulers
1918
+ and the foot title folded under the advanced toggle. */
1919
+ .ed-wave-settings-pop { display: flex; flex-direction: column; gap: 6px; width: 260px; }
1920
+ .ed-wave-set-cap { font-size: 11px; color: #8a8a8a; margin-top: 2px; }
1921
+ .ed-wave-set-row { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #333; }
1922
+ .ed-wave-set-label { flex: 0 0 44px; }
1923
+ .ed-wave-set-input {
1924
+ flex: 1 1 auto; min-width: 0; box-sizing: border-box; padding: 3px 6px; font-size: 12px;
1925
+ font-family: inherit; border: 1px solid #e2e2e2; border-radius: 4px; background: #fff; color: #222;
1926
+ }
1927
+ textarea.ed-wave-set-input { resize: vertical; }
1928
+ .ed-wave-set-scales { display: flex; gap: 4px; }
1929
+ .ed-wave-set-scales button, .ed-wave-set-advanced, .ed-wave-set-fix {
1930
+ border: 1px solid #e2e2e2; border-radius: 4px; background: #fff; color: #333;
1931
+ padding: 3px 10px; font-size: 12px; cursor: pointer;
1932
+ }
1933
+ .ed-wave-set-scales button:hover, .ed-wave-set-advanced:hover, .ed-wave-set-fix:hover { background: var(--md-ed-hover); }
1934
+ .ed-wave-set-scales button[aria-pressed="true"] { background: #1a73e8; color: #fff; border-color: #1a73e8; }
1935
+ .ed-wave-set-advanced { align-self: flex-start; }
1936
+ .ed-wave-set-advanced::after { content: " ⌄"; }
1937
+ .ed-wave-set-advanced[aria-expanded="true"]::after { content: " ⌃"; }
1938
+ .ed-wave-set-adv { display: flex; flex-direction: column; gap: 6px; }
1939
+ .ed-wave-set-adv[hidden] { display: none; }
1940
+ .ed-wave-set-overflow {
1941
+ display: flex; flex-direction: column; align-items: flex-start; gap: 6px; padding: 6px 8px;
1942
+ border: 1px dashed var(--md-ed-dirty); border-radius: 4px; font-size: 12px; color: #333;
1943
+ }
1944
+ .ed-wave-set-overflow[hidden] { display: none; }
1945
+ .ed-wave-set-fix { color: var(--md-ed-dirty); border-color: var(--md-ed-dirty); }
1946
+ /* The export menu's last line: what every export looks like. */
1947
+ .ed-wave-export-note {
1948
+ border-top: 1px solid #e2e2e2; margin-top: 4px; padding: 6px 10px 2px; font-size: 11px; color: #8a8a8a;
1949
+ }
1950
+ /* The chip beside a label wider than its segment; one press widens the
1951
+ cycles. Above the layer, so a press on it never paints. */
1952
+ .ed-wave-overflow-chip {
1953
+ position: absolute; z-index: 2; white-space: nowrap; padding: 1px 6px; font-size: 11px;
1954
+ line-height: 14px; border: 1px solid var(--md-ed-dirty); border-radius: 8px;
1955
+ background: var(--md-ed-surface); color: var(--md-ed-dirty); cursor: pointer;
1956
+ }
1957
+ .ed-wave-overflow-chip[hidden] { display: none; }
1958
+ .ed-wave-brush-grid { display: flex; flex-direction: column; gap: 6px; }
1959
+ .ed-wave-brush-group { display: flex; align-items: center; gap: 8px; }
1960
+ .ed-wave-brush-group-title { flex: 0 0 32px; font-size: 11px; color: #8a8a8a; }
1961
+ .ed-wave-brushes { display: flex; flex-wrap: wrap; gap: 4px; }
1962
+ /* Spec section 4.9: what the editor cannot place, right under the
1963
+ toolbar, in neutral colours, and it can be folded away. */
1964
+ .ed-wave-notice {
1965
+ display: flex; align-items: flex-start; gap: 8px; padding: 6px 12px;
1966
+ border-bottom: 1px solid #e2e2e2; background: #fafafa; color: #555; font-size: 12px;
1967
+ }
1968
+ .ed-wave-notice[hidden] { display: none; }
1969
+ .ed-wave-notice-text { flex: 1 1 auto; }
1970
+ .ed-wave-notice[data-folded] .ed-wave-notice-text {
1971
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1972
+ }
1973
+ .ed-wave-notice-fold {
1974
+ flex: 0 0 auto; border: 0; background: transparent; color: #1a73e8;
1975
+ font-size: 12px; cursor: pointer; padding: 0 4px;
1976
+ }
1977
+ /* The stage: the canvas's scroll container, the dialog's whole width.
1978
+ position: relative so the data-label field (wave-ui.js openDataEdit)
1979
+ is placed in its scrolled content box. */
1980
+ .ed-wave-stage {
1981
+ flex: 1 1 auto; min-height: 0; overflow: auto; padding: 8px; position: relative;
1982
+ }
1983
+ /* The engine's svg and the interaction layer share this box. The top
1984
+ padding is the room for the layer's cycle ruler, which sits above the
1985
+ engine's drawing (wave-ui.js RULER_GAP is the baseline's distance). */
1986
+ .ed-wave-canvas-host { position: relative; display: inline-block; padding-top: 20px; }
1987
+ .ed-wave-canvas-error { color: #b00020; padding: 8px; }
1988
+ .ed-wave-canvas { display: block; }
1989
+ /* The drawing is in the Tab cycle, so it has to say when it holds the
1990
+ keyboard. */
2064
1991
  .ed-wave-canvas:focus { outline: 2px solid #1a73e8; outline-offset: 2px; }
2065
- /* The discontinuity marker. The engine draws one gap <use> per pipe
2066
- character; drawing none left the two pictures disagreeing on a lane whose
2067
- levels agreed exactly. v3.5.0 Task 11 fix round 4 audit: drawLane's own
2068
- comment already calls this "last so it sits over the trace it
2069
- interrupts" — purely decorative, drawn on top purely to be seen, the
2070
- exact shape of thing that swallowed a press meant for the bus label
2071
- underneath it (see .ed-wave-selection above). A gap cycle inside a bus
2072
- run can land at that run's own label position, so pointer-events: none
2073
- here too, on the same principle, not because a concrete collision was
2074
- reproduced. */
2075
- .ed-wave-gap { stroke: #111; stroke-width: 2; fill: none; pointer-events: none; }
2076
- /* v3.5.0 Task 6: edge: relationship lines, drawn by wave-ui.js's
2077
- renderEdges() after every lane. Named .ed-wave-edge-* (with a hyphen)
2078
- rather than .ed-wave-edge, which is already the brick-to-brick
2079
- transition tick inside ONE lane (two rules above) — a different mark
2080
- with a different meaning, and CSS class matching is exact-token so the
2081
- two never collide, but the names are kept visually distinct too.
2082
- Colours match the engine's own arc-shape.js style string
2083
- (stroke:#0041c4) so a line drawn here and the same line in the
2084
- WaveDrom preview beside it read as the same colour. */
2085
- .ed-wave-edge-path { stroke: #0041c4; stroke-width: 1; fill: none; }
2086
- .ed-wave-edge-path.is-on { stroke: #1a73e8; stroke-width: 2; }
2087
- /* v3.5.0 Task 8: the edge an in-flight endpoint drag is dragging, drawn at
2088
- its own ACTUAL (still-committed) position — the document does not
2089
- change until the drop — but faded so it reads as "not where this will
2090
- end up" once wave-ui.js's own dashed .ed-wave-edge-pending preview line
2091
- (same mechanism Task 7's not-yet-written edge already uses) is standing
2092
- next to it. Combines with .is-on: the dragged edge is always the
2093
- selected one (renderEdges()'s own comment), so both classes are present
2094
- together and this only lowers opacity on top of that colour. */
2095
- .ed-wave-edge-path.is-drag-source { opacity: .35; }
2096
- /* The invisible, wide-stroke click target layered over each visible path
2097
- (see renderEdges()'s own comment) — pointer-events: stroke is what makes
2098
- a transparent stroke hit-testable at all, since a stroke that paints
2099
- nothing is not visiblePainted by the SVG default. */
2100
- .ed-wave-edge-hit { stroke: transparent; stroke-width: 10; fill: none; pointer-events: stroke; }
2101
- /* v3.5.0 Task 11 fix round 4 audit, closed in round 5: this text (drawn
2102
- by renderEdges, AFTER .ed-wave-edge-hit) is the same shape of hazard as
2103
- .ed-wave-selection and .ed-wave-gap above — purely decorative, sits on
2104
- top of the fat invisible click target a press aimed at SELECTING the
2105
- edge is meant to land on. Round 4 found this but could not apply
2106
- pointer-events: none to the bare class: ed-wave-edge-label is not this
2107
- text's own class alone — wave-ui.js's sidebar edgeLabelInput (the real,
2108
- keyboard-and-mouse text field for editing an edge's label) uses the
2109
- IDENTICAL class name, and a blanket rule would have also landed on
2110
- that input field and blocked clicking into it with the mouse.
2111
- Round 5's fix: scope the SELECTOR by element instead of renaming
2112
- anything. text.ed-wave-edge-label only ever matches the SVG <text>
2113
- wave-ui.js draws on the canvas — the sidebar control is an <input>, a
2114
- different element entirely, and keeps its own default pointer-events
2115
- untouched no matter what class it shares. */
2116
- text.ed-wave-edge-label { pointer-events: none; }
2117
- .ed-wave-edge-label { font-size: 11px; fill: #333; }
2118
- /* v3.6.0 Task 8: the canvas's own name column — a mirror of the rail's
2119
- .ed-wave-lane-name / .ed-wave-group (see the comment above
2120
- .ed-wave-lane-rows). Unlike .ed-wave-edge-label these two class names
2121
- are not shared with any non-SVG control, so pointer-events: none goes
2122
- straight on the class rather than needing the element-scoped selector
2123
- that rule uses. MEASURED (task-8-report.md): the longest lane name in
2124
- this repo's fixtures is 12 chars ("he said \"hi\""), which at 11px in
2125
- both the app's default sans-serif and the worst case (monospace)
2126
- measures well under the 110px a top-level name has to fit in
2127
- (nameColWidth 120 - NAME_GAP 10), so SIZES.nameColWidth stays 120. */
2128
- .ed-wave-lane-label, .ed-wave-group-label { pointer-events: none; }
2129
- .ed-wave-lane-label { font-size: 11px; fill: #333; }
2130
- .ed-wave-group-label {
2131
- font-size: 11px; fill: #555; writing-mode: vertical-rl; text-orientation: mixed;
2132
- }
2133
- /* v3.6.0 Task 9: the cycle ruler and the head/foot bands — see
2134
- wave-draw.js's drawRuler()/drawBand() comment for the geometry (tick on
2135
- boundaries, tock at centres, both filtered by every) and why this is
2136
- read-only with respect to the document. Purely decorative like the name
2137
- column above it, so pointer-events: none on the same principle. The
2138
- ruler's own fill matches the pinned engine's own muted class (fill
2139
- #aaa, node_modules/wavedrom's waveSkin CSS) so a tick/tock painted here
2140
- reads the same shade as the identical mark in the WaveDrom preview
2141
- beside it; the head/foot titles use the darker ed-wave-lane-label shade
2142
- instead, matching the engine's own plain black fill for head.text /
2143
- foot.text closely enough at this scale without introducing a third
2144
- grey. */
2145
- .ed-wave-ruler-tick, .ed-wave-ruler-tock, .ed-wave-head-text, .ed-wave-foot-text {
2146
- pointer-events: none;
1992
+ /* The interaction layer's marks (wave-draw.js createLayer). None of them
1993
+ takes a press: the layer svg itself does, and the pointer is resolved
1994
+ to a cell by geometry. The exceptions are a transition dot, which takes
1995
+ it through its wider transparent ring, and the edges' hit twins below. */
1996
+ .ed-wave-ruler-num, .ed-wave-ruler-plus, .ed-wave-row-hover, .ed-wave-hover-col,
1997
+ .ed-wave-selection, .ed-wave-cursor, .ed-wave-dot-mark, .ed-wave-pending,
1998
+ .ed-wave-edge-sel, .ed-wave-edge-end, .ed-wave-overflow, .ed-wave-drop-line { pointer-events: none; }
1999
+ /* Task 8: where a lane dragged by its grip will land. */
2000
+ .ed-wave-drop-line { stroke: #1a73e8; stroke-width: 2; }
2001
+ .ed-wave-ruler-num { font-size: 11px; fill: #aaa; }
2002
+ .ed-wave-ruler-num.is-on { fill: #1a73e8; font-weight: bold; }
2003
+ .ed-wave-ruler-plus-glyph { font-size: 14px; font-weight: bold; fill: #1a73e8; }
2004
+ /* Task 7: the ruler's plus inserts a cycle where it stands, so its ring
2005
+ (wave-draw.js createLayer) is the one ruler mark that takes a press. */
2006
+ .ed-wave-ruler-plus-hit { pointer-events: all; cursor: pointer; }
2007
+ .ed-wave-row-hover, .ed-wave-hover-col { fill: var(--md-ed-sel); }
2008
+ .ed-wave-selection { fill: rgba(26,115,232,.18); stroke: #1a73e8; stroke-width: 1; }
2009
+ /* The keyboard's own cell, deliberately not the selection's mark: the
2010
+ selection is the range an operation acts on, the cursor the one cell
2011
+ the next keystroke paints, and both can be on screen at once. */
2012
+ .ed-wave-cursor { fill: none; stroke: #d93025; stroke-width: 2; }
2013
+ .ed-wave-dot { cursor: crosshair; }
2014
+ .ed-wave-dot-hit { pointer-events: all; }
2015
+ .ed-wave-dot-mark { fill: #fff; stroke: #1a73e8; stroke-width: 1.5; }
2016
+ .ed-wave-dot.is-hot .ed-wave-dot-mark { fill: #1a73e8; stroke: #0041c4; }
2017
+ .ed-wave-pending { stroke: #1a73e8; stroke-width: 2; stroke-dasharray: 5 4; fill: none; }
2018
+ .ed-wave-edge-sel { stroke: #1a73e8; stroke-width: 2; fill: none; }
2019
+ .ed-wave-edge-end { fill: #1a73e8; stroke: #fff; stroke-width: 1.5; }
2020
+ .ed-wave-overflow { fill: none; stroke: var(--md-ed-dirty); stroke-width: 1.5; stroke-dasharray: 3 2; }
2021
+ /* The bus data-label field, placed over its cell by openDataEdit; only
2022
+ the skin lives here. */
2023
+ .ed-wave-data-input {
2024
+ position: absolute; z-index: 1; box-sizing: border-box; text-align: center;
2025
+ font-size: 11px; padding: 0 2px; border: 1px solid #1a73e8; border-radius: 2px;
2026
+ background: #fff; color: #222;
2147
2027
  }
2148
- .ed-wave-ruler-tick, .ed-wave-ruler-tock { font-size: 11px; fill: #aaa; }
2149
- .ed-wave-head-text, .ed-wave-foot-text { font-size: 11px; fill: #333; }
2150
- .ed-wave-edge-handle { fill: #1a73e8; stroke: #ffffff; }
2151
- .ed-wave-edge-arrow { fill: #0041c4; }
2152
- .ed-wave-edge-tee { stroke: #0041c4; }
2153
- /* v3.5.0 Task 7: the pending line an armed drag or keyboard two-step is
2154
- standing on, from wave-ui.js's renderCanvas() — UI state only, never a
2155
- document read, so it is dashed rather than the solid .ed-wave-edge-path
2156
- colour to read as "not written yet". pointer-events: none so it can
2157
- never itself become the thing a press lands on. */
2158
- .ed-wave-edge-pending {
2159
- stroke: #1a73e8; stroke-width: 2; stroke-dasharray: 5 4; fill: none;
2160
- pointer-events: none;
2028
+ /* Task 8 (spec section 4.4): the name column. Each row and each group
2029
+ title has a grip, laid over the column by wave-ui.js (the box sits on
2030
+ the canvas host padding box, above the layer). A grip shows for the row
2031
+ the pointer is over, while it has the keyboard and while its menu is
2032
+ open; otherwise it is invisible and takes no press, so the column under
2033
+ it stays the name the pointer means. */
2034
+ .ed-wave-grips { position: absolute; left: 0; top: 0; width: 0; height: 0; z-index: 2; }
2035
+ .ed-wave-grip {
2036
+ position: absolute; width: 16px; height: 22px; padding: 0; margin: 0;
2037
+ display: flex; align-items: center; justify-content: center;
2038
+ border: 0; border-radius: 4px; background: var(--md-ed-surface); color: var(--md-ed-glyph);
2039
+ font-size: 13px; line-height: 1; cursor: pointer; opacity: 0; pointer-events: none;
2040
+ }
2041
+ .ed-wave-grip.is-on, .ed-wave-grip:focus, .ed-wave-grip[aria-expanded="true"] {
2042
+ opacity: 1; pointer-events: auto;
2043
+ }
2044
+ .ed-wave-grip:hover, .ed-wave-grip[aria-expanded="true"] { background: var(--md-ed-hover); color: #333; }
2045
+ .ed-wave-grip:focus { outline: 2px solid #1a73e8; outline-offset: 0; }
2046
+ /* The in-place name field over a signal name (right-aligned, like the
2047
+ name the engine draws) or a group title. */
2048
+ .ed-wave-rename {
2049
+ position: absolute; z-index: 3; box-sizing: border-box; text-align: right;
2050
+ font-size: 14px; padding: 0 6px; border: 1px solid #1a73e8; border-radius: 3px;
2051
+ background: #fff; color: #222;
2161
2052
  }
2162
- /* v3.6.0 Task 11: the transition dots wave-draw.js's drawTransitions()
2163
- paints while edge-creation is armed -- the visual answer to "where can
2164
- I aim". pointer-events: none for the same reason ed-wave-selection and
2165
- the other UI-state marks above are: the actual hit-testing runs through
2166
- geometry.boundaryAt against the pointer's raw coordinates
2167
- (wave-ui.js's cellFromEvent/onCanvasHover), never through this circle
2168
- as a DOM target, so it must never itself swallow the press it exists
2169
- to help aim. The [data-hot] variant is the same dot, larger and
2170
- filled solid, for the one nearest the pointer right now
2171
- (state.hoverBoundary in drawTransitions). */
2172
- .ed-wave-transition {
2173
- fill: #ffffff; stroke: #1a73e8; stroke-width: 1.5; pointer-events: none;
2174
- }
2175
- .ed-wave-transition[data-hot] { fill: #1a73e8; stroke: #0041c4; }
2176
- /* v3.6.0 Task 13: the old flex-basis/border/padding here were for
2177
- .ed-wave-preview as a direct flex child of .ed-wave-body; it now
2178
- lives inside .ed-wave-section-body (the 預覽 panel), which already
2179
- supplies the column width and padding, so only its own scroll behaviour
2180
- is left to set — a wide diagram's preview SVG must scroll within the
2181
- panel rather than stretching the whole 340px column. */
2182
- .ed-wave-preview { overflow: auto; max-height: 260px; }
2183
- .ed-wave-preview-label { font-size: 11px; color: #666; margin-bottom: 6px; }
2184
- /* v3.6.0 Task 13: the right-hand column — five collapsible panels
2185
- (關聯線/文件/Lane/預覽/原始碼) replacing the old direct
2186
- .ed-wave-preview column. 'flex: 0 0 340px' (not '0 1'): unlike the
2187
- old preview column, this one does not shrink when the canvas wants
2188
- more room — width-budget wins go through .ed-wave-panel's own cap
2189
- above, not by starving this column, because starving it re-introduces
2190
- the exact 160px-minimum squeeze this task's own .ed-wave-lanes
2191
- comment measured as the 800x600 defect. Below 1100px it collapses to a
2192
- 28px rail instead (media query below) for the identical reason. */
2193
- /* v3.6.0 Task 13 fix round 4: box-sizing: border-box added. wave-ui.js's
2194
- clearSelectedEdgeOfSide() sets an inline width (in border-box, i.e.
2195
- rendered, pixels — the same unit getBoundingClientRect() reports
2196
- everywhere else it measures this element) to keep the selected edge's
2197
- own handle clear of this panel when expanded. Under the default
2198
- content-box sizing, width excludes padding (16px) and the border
2199
- (1px), so a JS-computed width landed 17px narrower on screen than
2200
- intended — MEASURED, not assumed: an inline width of 310px rendered at
2201
- 327px. border-box makes width mean the same thing in CSS and in
2202
- getBoundingClientRect(), so the two agree without wave-ui.js having to
2203
- hardcode this rule's own padding/border numbers into its arithmetic. */
2204
- .ed-wave-side {
2205
- box-sizing: border-box; flex: 0 0 340px; border-left: 1px solid #e2e2e2;
2206
- overflow: auto; padding: 8px; display: flex; flex-direction: column; gap: 8px;
2207
- }
2208
- .ed-wave-section { border: 1px solid #ececec; border-radius: 6px; }
2209
- .ed-wave-section-title {
2210
- display: block; width: 100%; text-align: left; border: 0; background: #fafafa;
2211
- font: inherit; font-size: 11px; letter-spacing: .5px; color: #555;
2212
- padding: 5px 8px; cursor: pointer; border-radius: 6px 6px 0 0;
2213
- }
2214
- .ed-wave-section-body { display: none; padding: 8px; }
2215
- /* v3.6.0 Task 13 fix round 3: data-open is this SECTION's own toggle
2216
- state (wave-panels.js's section()) and says NOTHING about whether the
2217
- RAIL is currently showing it — those are two independent attributes on
2218
- two different elements. A section can report data-open with a
2219
- zero-size, unhittable body the whole time .ed-wave-side itself is
2220
- collapsed to its <1100px 28px rail (below): 關聯線/文件/Lane all default
2221
- data-open, and none of that changes just because the rail hides them.
2222
- Left deliberately this way rather than making data-open track actual
2223
- visibility — that would conflate "this section is open" with "the rail
2224
- happens to be showing right now", and toggling ONE section would then
2225
- have to silently relabel every OTHER already-open section's own state
2226
- too. What actually closes the gap this could otherwise leave (a control
2227
- that LOOKS reachable in the DOM but is not) is wave-ui.js's render()
2228
- expanding the rail on the one transition that matters (selecting an
2229
- edge) rather than this attribute pretending to mean something it does
2230
- not. */
2231
- .ed-wave-section[data-open] .ed-wave-section-body { display: block; }
2232
- .ed-wave-section-body label { display: flex; align-items: center; gap: 6px; margin: 4px 0; }
2233
- .ed-wave-section-body label span { flex: 0 0 96px; font-size: 12px; color: #555; }
2234
- .ed-wave-section-body input {
2235
- flex: 1 1 auto; min-width: 0; font-size: 12px; padding: 2px 4px;
2236
- }
2237
- .ed-wave-source {
2238
- margin: 0; font-size: 11px; line-height: 1.45; white-space: pre;
2239
- overflow: auto; max-height: 260px;
2240
- }
2241
- /* 視窗窄於這條線時右欄收成一條直立標籤,展開時覆蓋在畫布上 —— 否則
2242
- 800x600 的測試視窗下畫布會被 340px 固定寬的側欄吃掉大半,拖曳手勢會
2243
- 不可用(v3.6.0 Task 13 自己的 .ed-wave-lanes 註解量到的舊值:只剩
2244
- 282px)。收起時仍是普通文件流寬度(28px),展開時疊在畫布上方而不擠
2245
- 壓它。data-expanded 有三個來源:wave-panels.js 的 section() 點擊 title、
2246
- 同檔案的 collapseBtn 反過來移除它、以及 fix round 3 新增的
2247
- wave-panels.js 的 expandSide()(由 wave-ui.js 的 render() 在
2248
- selectedEdge 從 null 變成有值的那一拍呼叫——選取一條 edge 之後,
2249
- 整個回饋只長在這個側欄的關聯線面板裡,窄視窗下不展開就等於使用者
2250
- 什麼都看不到、也按不到)。 */
2251
- /* v3.6.0 Task 13 fix round 1: the collapse-back button. Hidden everywhere
2252
- except the one state it exists for — the <1100px rail, expanded — so it
2253
- never shows, and never sits in the tab order, at a wide viewport or in
2254
- the rail's own default collapsed state. */
2255
- .ed-wave-side-collapse { display: none; }
2256
- @media (max-width: 1100px) {
2257
- .ed-wave-side { flex: 0 0 28px; padding: 4px 2px; }
2258
- .ed-wave-side:not([data-expanded]) .ed-wave-section-body { display: none; }
2259
- .ed-wave-side[data-expanded] {
2260
- position: absolute; right: 0; top: 0; bottom: 0; width: 340px;
2261
- background: #ffffff; z-index: 3; flex: 0 0 340px; padding: 8px;
2262
- }
2263
- .ed-wave-side[data-expanded] .ed-wave-side-collapse {
2264
- display: block; width: 100%; text-align: right; border: 0;
2265
- background: transparent; font-size: 12px; color: #555; cursor: pointer;
2266
- padding: 2px 4px 6px;
2267
- }
2053
+ .ed-wave-rename.is-group { text-align: left; }
2054
+ /* The add-a-signal row under the last lane. */
2055
+ .ed-wave-add-lane {
2056
+ display: block; margin: 4px 0 0; border: 0; border-radius: 4px; background: transparent;
2057
+ color: #1a73e8; font-size: 12px; padding: 4px 8px; cursor: pointer;
2058
+ }
2059
+ .ed-wave-add-lane:hover, .ed-wave-add-lane:focus { background: var(--md-ed-hover); }
2060
+ /* Spec section 4.3: the floating toolbar over a selected run of cycles,
2061
+ placed against the panel by wave-ui.js (above the run, or below it when
2062
+ there is no room). Under the popovers (z-index 4), so 電位's grid opens
2063
+ over it. */
2064
+ .ed-wave-range {
2065
+ position: absolute; z-index: 3; display: flex; align-items: center; gap: 2px;
2066
+ padding: 3px; background: #fff; border: 1px solid #e2e2e2; border-radius: 6px;
2067
+ box-shadow: 0 4px 16px var(--md-ed-shadow); white-space: nowrap;
2068
+ }
2069
+ .ed-wave-range button {
2070
+ border: 0; border-radius: 4px; background: transparent; color: #333;
2071
+ padding: 4px 8px; font-size: 12px; cursor: pointer;
2072
+ }
2073
+ .ed-wave-range button:hover, .ed-wave-range button:focus { background: var(--md-ed-hover); }
2074
+ /* Task 9 (spec section 4.5): the edges. The engine draws them under the
2075
+ layer, so the layer carries a transparent twin of each for a press to
2076
+ land on: the line (hit on its wide stroke only), the label box and an
2077
+ unlabelled self-loop ring (hit on their area). A selected edge has a
2078
+ ring at each end that takes a press, under its visible mark. The
2079
+ selection is drawn with the engine path and its matrix, so its width
2080
+ must not scale with it. */
2081
+ .ed-wave-edge-hit { pointer-events: stroke; cursor: pointer; }
2082
+ .ed-wave-edge-label-hit, .ed-wave-edge-loop-hit { pointer-events: all; }
2083
+ .ed-wave-edge-end-hit { pointer-events: all; cursor: move; }
2084
+ .ed-wave-edge-sel { vector-effect: non-scaling-stroke; }
2085
+ .ed-wave-note-sel { fill: none; stroke: #1a73e8; stroke-width: 1.5; pointer-events: none; }
2086
+ /* The toolbar beside a selected edge or note (right of its label),
2087
+ placed against the panel like the range toolbar; 更多 lists its shapes
2088
+ on a second row. */
2089
+ .ed-wave-edge-bar {
2090
+ position: absolute; z-index: 3; display: flex; flex-wrap: wrap; align-items: center; gap: 2px;
2091
+ max-width: 360px; padding: 3px; background: #fff; border: 1px solid #e2e2e2; border-radius: 6px;
2092
+ box-shadow: 0 4px 16px var(--md-ed-shadow); white-space: nowrap;
2093
+ }
2094
+ .ed-wave-edge-group { display: flex; gap: 1px; padding-right: 3px; margin-right: 1px; border-right: 1px solid #e2e2e2; }
2095
+ .ed-wave-edge-bar button {
2096
+ border: 0; border-radius: 4px; background: transparent; color: #333;
2097
+ padding: 4px 8px; font-size: 12px; cursor: pointer;
2098
+ }
2099
+ .ed-wave-edge-bar button:hover, .ed-wave-edge-bar button:focus { background: var(--md-ed-hover); }
2100
+ .ed-wave-edge-bar button[aria-pressed="true"] { background: #1a73e8; color: #fff; }
2101
+ .ed-wave-edge-bar button.is-danger { color: var(--md-ed-err); }
2102
+ .ed-wave-edge-more-list { display: flex; flex-wrap: wrap; gap: 1px; flex-basis: 100%; }
2103
+ .ed-wave-edge-more-list[hidden] { display: none; }
2104
+ .ed-wave-edge-more-list button { font-family: monospace; }
2105
+ /* The one-line field over an edge's label or a point note. */
2106
+ .ed-wave-edge-input {
2107
+ position: absolute; z-index: 3; box-sizing: border-box; text-align: center;
2108
+ font-size: 12px; padding: 0 4px; border: 1px solid #1a73e8; border-radius: 3px;
2109
+ background: #fff; color: #222;
2268
2110
  }
2269
- .ed-wave-status {
2270
- border-top: 1px solid #e2e2e2; padding: 6px 12px; font-size: 12px;
2271
- color: #444; min-height: 18px;
2111
+ /* Spec section 4.8: the bottom line says what can be done next, never
2112
+ what just happened (results go to the toast). Task 11: the sentence
2113
+ follows the state, and 「? 全部快捷鍵」 sits fixed at its right end. */
2114
+ .ed-wave-hint {
2115
+ display: flex; align-items: center; gap: 12px;
2116
+ border-top: 1px solid #e2e2e2; padding: 3px 8px 3px 12px; font-size: 12px; color: #444; min-height: 24px;
2117
+ }
2118
+ .ed-wave-hint-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2119
+ .ed-wave-hint-help {
2120
+ flex: 0 0 auto; border: 0; border-radius: 4px; background: transparent; color: #1a73e8;
2121
+ font-size: 12px; padding: 3px 8px; cursor: pointer;
2122
+ }
2123
+ .ed-wave-hint-help:hover, .ed-wave-hint-help[aria-expanded="true"] { background: var(--md-ed-hover); }
2124
+ /* Task 11: the shortcut card, in the middle of the editor (wave-ui.js
2125
+ centres it), above every other popover and floating toolbar. */
2126
+ .ed-wave-shortcuts {
2127
+ z-index: 5; box-sizing: border-box; width: min(600px, calc(100% - 16px));
2128
+ max-height: calc(100% - 16px); overflow: auto; padding: 10px 14px 14px;
2129
+ }
2130
+ .ed-wave-shortcuts:focus { outline: none; }
2131
+ .ed-wave-shortcuts-head { display: flex; align-items: center; margin-bottom: 4px; }
2132
+ .ed-wave-shortcuts-title { flex: 1 1 auto; font-weight: bold; font-size: 14px; color: #222; }
2133
+ .ed-wave-shortcuts-close {
2134
+ border: 0; border-radius: 4px; background: transparent; color: #555;
2135
+ width: 26px; height: 26px; font-size: 13px; cursor: pointer;
2136
+ }
2137
+ .ed-wave-shortcuts-close:hover { background: var(--md-ed-hover); }
2138
+ .ed-wave-shortcuts-body { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; }
2139
+ .ed-wave-shortcut-title { font-weight: bold; font-size: 12px; color: #444; margin: 6px 0 4px; }
2140
+ .ed-wave-shortcut-row { display: flex; align-items: baseline; gap: 10px; font-size: 12px; color: #333; padding: 2px 0; }
2141
+ .ed-wave-shortcut-keys { flex: 0 0 92px; display: flex; flex-wrap: wrap; gap: 3px; }
2142
+ .ed-wave-shortcut-keys kbd {
2143
+ font-family: inherit; font-size: 11px; line-height: 16px; padding: 0 4px;
2144
+ border: 1px solid #e2e2e2; border-radius: 3px; background: #fff; color: #222; white-space: nowrap;
2145
+ }
2146
+ /* The cursor narration: for a screen reader only, so visually hidden but
2147
+ never display: none (a live region that is not rendered is not read). */
2148
+ .ed-wave-live {
2149
+ position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
2150
+ overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
2272
2151
  }
2273
2152
  .ed-wave-parse-error { padding: 16px; }
2274
2153
  .ed-wave-parse-message { color: #b00020; font-weight: bold; }
@@ -6377,6 +6256,9 @@ if (require.main === module) {
6377
6256
  const [,, src, dst] = process.argv;
6378
6257
  const BAKE_SVG = process.argv.slice(4).includes('--bake-svg');
6379
6258
  const LANG_ARG = (process.argv.slice(4).find((a) => a.startsWith('--lang=')) || '').slice('--lang='.length) || undefined;
6259
+ // bin/md2doc.js renders each format of one input in its own process and
6260
+ // passes this to every one after the first, so --html --pdf warns once.
6261
+ const WAVE_WARN = !process.argv.slice(4).includes('--no-wave-warn');
6380
6262
  if (!src || !dst) {
6381
6263
  console.error('Usage: node md2doc.js <input.md> <output.html|pdf>');
6382
6264
  process.exit(1);
@@ -6386,7 +6268,7 @@ if (require.main === module) {
6386
6268
 
6387
6269
  (async () => {
6388
6270
  const mdText = fs.readFileSync(src, 'utf8');
6389
- const { html } = await renderMarkdown(mdText, path.resolve(src), { lang: LANG_ARG });
6271
+ const { html } = await renderMarkdown(mdText, path.resolve(src), { lang: LANG_ARG, warnWaveOverflow: WAVE_WARN ? src : undefined });
6390
6272
  let finalHtml = html;
6391
6273
 
6392
6274
  if (ext === '.html') {