fix(design-md): edits follow a symlinked DESIGN.md, keep the BOM and the majority line ending, refuse an unclosed fence

- `mark`, `convert --write`, and the design binary's extraction replaced a
  symlinked DESIGN.md (a docs-site layout) with a regular file and left the
  real target untouched; both writers resolve the link first.
- A single stray CRLF flipped a whole LF file to CRLF: the editors now keep
  the majority ending. A UTF-8 BOM broke format detection and ended up
  mid-file after `mark`; it is recognized and kept at byte 0.
- Re-running `mark` on a marked file deleted the blank line after the marker
  (`\s*$` matched across the newline); the marker regexes use `[ \t]*`.
- Fences: readers follow markdown (an unclosed fence runs to EOF); the
  text-level editors refuse such a file with DesignMdEditRefused
  (DESIGN_MD_EDIT_REFUSED) instead of splicing the wrong section, and the
  design binary reports that and leaves the file alone.
- needsQuotes also quotes a scalar containing ` #` (an inline-comment
  shape parsed back as a truncated value).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-09-08 18:33:22 +00:00
co-authored by Claude Fable 5.1
parent 8d709c8f29
commit cb9ba7a3e2
4 changed files with 103 additions and 34 deletions
+3 -1
View File
@@ -30,8 +30,10 @@ import {
parseDesignMd, detectFormat, convertLegacy, renderDesignMd, tokensFlat, insertMarker,
type DesignMdDoc, type FormatChoice, FORMAT_CHOICES } from '../lib/design-md';
/** The file itself, through any symlink (a `DESIGN.md -> docs/DESIGN.md` layout must edit the target, never replace the link). */
function resolveFile(arg?: string): string {
return path.resolve(arg ?? 'DESIGN.md');
const p = path.resolve(arg ?? 'DESIGN.md');
try { return fs.realpathSync(p); } catch { return p; }
}
function load(file: string): { text: string; doc: DesignMdDoc } | null {