# G-045 punk::args definition authoring ergonomics: record continuation, @cmd unindented fields, constructed-definition normalization Status: active Scope: src/modules/punk/args-999999.0a1.0.tm (record parsing in resolve, tstr interplay, arg_error @cmd rendering), src/tests/modules/punk/args/testsuites/ (rendering.test/defquoting.test as the safety net), src/modules/punk-999999.0a1.0.tm (::punk::helptopic::define_docs de-hacked as the consumer proof) Goal: authoring punk::args definitions no longer requires backslash line-continuations or ad-hoc workarounds for multi-line records and constructed definitions - a parser-recognised record-continuation mechanism (candidate: an unquoted trailing -& token, with the detail file recording the collision analysis and the element-count disambiguation alternative), -unindentedfields honoured for @cmd fields, and constructed (string-built) definitions able to opt into the same whole-block indent normalization file-style definitions get - with the container quoting rules (braced=literal, quoted=Tcl backslash semantics, \$\{ escape) promoted from defquoting.test into the define documentation. Acceptance: a definition using the chosen record-continuation mechanism parses identically to its backslash-continuation equivalent (existing definitions unchanged - continuation is additive), with the token's collision rules documented and an escape/rejection story for values that legitimately match it; @cmd -help/-summary honour -unindentedfields (the rendering.test GAP rendering_unindentedfields_cmd_help_GAP flips to aligned); a constructed definition can request whole-block normalization so embedded continuation indentation behaves as in file-style definitions (the rendering_constructed_def_indent_characterization expectations updated to the chosen semantics), and ::punk::helptopic::define_docs drops its manual pre-normalization to prove it; the quoting rules from defquoting.test appear in the punk::args::define -help documentation; the full punk::args suite (128 tests incl. the rendering invariants: nesting independence, relative-indent preservation) passes with GAP tests flipped, none weakened. ## Context Drafted 2026-07-09 after the tests-first characterization pass (user direction: implement tests before changes in this area). The pain points, in the user's framing: - The define-block syntax deliberately avoids Tcl dict syntax for human readability, which led to backslash line-continuations and a complex interplay between record parsing in punk::args::resolve and tstr (especially ${...} expansions keeping whitespace aligned during multiline substitutions). - Constructed (string-built) definitions - ::punk::helptopic::define_docs and punk::args::ensemble_subcommands_definition are the in-tree examples - have poor ergonomics for getting indentation right: unlike file-style definitions they receive no whole-block indent normalization (proven: rendering.test rendering_constructed_def_indent_characterization - continuations at exactly 4 spaces align, other depths leak), so builders pre-normalize by hand (undent/trim hacks, \n-relative authoring). - A key property of normal definitions that must be preserved: they do not depend on the indentation of the code block they appear in (proven and pinned: rendering_nesting_independence_plain / _tstr). - The user's experiment used an ad-hoc `--` flag followed by a braced line-spanning empty string as a de-facto record continuation - functional but not designed-for, and unsatisfying. ## The -& record-continuation candidate (implemented 2026-07-12 - see Progress increment 3) The user's proposal: a special token the record parser recognises as record continuation, e.g. an unquoted `-&` at end of line. Design considerations recorded for implementation: - Collision risk is low but not zero (a value or flag legitimately spelled -&). Candidate rules: only an UNQUOTED trailing `-&` at end of a record line counts; and/or use the element count of the assembled record to disambiguate (an odd element count where pairs are expected implies the trailing token is a continuation marker, not a value). The detail decision must include an escape story for a literal trailing -& value (bracing it suffices if 'unquoted' is part of the rule). - Detection must happen in the record parser (resolve) before/alongside line splitting - the same place backslash-continuation assembly effectively happens via Tcl's own parse of braced blocks today. Unlike backslash-newline, -& survives inside braced blocks of CONSTRUCTED definitions too - which is precisely why it helps: string-built defs cannot use backslash-newline ergonomically (it is consumed by the building code's own quoting). Decision as implemented (punk::args 0.7.0): - Rule: unquoted trailing `-&` only - the token must be a bare word preceded by whitespace (or be the whole line) and be the last element on the line; trailing whitespace after it is tolerated (deliberately more forgiving than raw backslash-newline, whose invisible-trailing-whitespace failure is a classic trap). - Escape story: brace or double-quote a literal trailing -& value ({-&}) - the raw line then ends with the closing delimiter and never matches. A -& mid-line, as a word suffix (abc-&), or on a line inside a still-open braced/quoted value is data. - Element-count disambiguation: NOT implemented. The positional rule is deterministic and locally decidable per line; element counting would require assembling and list-parsing the record first (fragile against in-progress records) and gives confusing action-at-a-distance when a distant key/value slips the count. Rejected rather than deferred - the escape story covers the residual collision (a literal trailing -default -& must be braced). - Assembly semantics: byte-identity with the backslash equivalent. Key finding: for braced (file-style) definitions Tcl itself collapses backslash-newline plus following whitespace to a single space BEFORE the text reaches the record splitter - so -& is implemented the same way (token dropped, single-space join, next line's leading whitespace collapsed), making the assembled record byte-identical to its backslash-continued twin. Proven by parse-result and rendered-table equality in recordcontinuation.test. - Reserved-word consequence: an argument cannot be NAMED -& via a record line ending in the bare token (e.g. a lone '-& ' line would read as a continuation). Any such need is met by bracing. Considered acceptable and documented here rather than guarded in code. ## @cmd -unindentedfields Accepted but ignored today (arg_error's cmd_info rendering carries an '#unindentedfields ?' todo; pinned by rendering_unindentedfields_cmd_help_GAP: left-margin-authored @cmd help renders its first line +4 vs continuations). Honouring it makes left-margin authoring available for @cmd -help/-summary as it already is for argument -help and -choicelabels. ## Constructed-definition normalization Options to decide at implementation (record the choice): - an explicit define option/directive requesting whole-block undent of the supplied text - or automatic common-prefix normalization when a definition arrives as a single text block (risk: changing existing constructed defs' rendering - the characterization test documents current expectations to update deliberately) ::punk::helptopic::define_docs currently pre-normalizes by hand (undent/trim + \n-relative help strings + -unindentedfields) - it converts to the new mechanism as the proof, and the user's uncommitted experimental hack in punk.tm is superseded. ## Quoting documentation defquoting.test pinned the container rules (braced values fully literal - $, [], two-char \n, bare backslashes; quoted values get Tcl backslash semantics - \n newline, \\ -> \ - with $/[] still literal; \$\{...\} renders a literal ${...} in tstr-processed blocks). Promote these rules into the punk::args::define -help documentation so documenters (e.g. punk::imap4's {\Deleted}/{$MDNSent} choice values) have them stated, not just tested. ## Alternatives considered - Keeping backslash continuations + manual pre-normalization as the documented way - rejected: the friction is proven by two in-tree constructed-def workarounds and the user's punk.tm experiments. - Tcl dict syntax for definitions - rejected long ago by design (readability). - The ad-hoc `--` + braced-gap trick as the blessed continuation - rejected: exploits parser behaviour that was never a contract. ## Notes - Safety net: src/tests/modules/punk/args/testsuites/args/rendering.test (15 tests: nesting independence, relative-indent preservation, unindentedfields, constructed-def characterization, multiline tstr insertions, @dynamic) and defquoting.test (container quoting rules). GAPs to flip here: rendering_unindentedfields_cmd_help_GAP, rendering_constructed_def_indent_characterization (expectations updated to chosen semantics). - Related: G-046 (deferred -help resolution and rendering fixes - same code neighbourhood; sequencing between the two goals is free but they touch resolve together), G-044 (completion consumes the documentation corpus these ergonomics grow). - Session records 2026-07-09: probe scripts renderprobe*.tcl (scratchpad), the user's experimental define_docs hack (uncommitted punk.tm working-tree diff at the time of drafting - superseded by this goal when implemented). - Archived-goal references in this file: G-046 achieved 2026-07-10 (goals/archive/G-046-punkargs-deferred-help-and-fixes.md). ## Progress ### 2026-07-12 increment 1: @cmd -unindentedfields honoured (punk::args 0.6.1) - arg_error's cmd-help display transform (undent " "+help, max 4 - the '#unindentedfields ?' todo site) is now gated by "-help" membership in the @cmd line's -unindentedfields list, mirroring the existing per-argument gate. The single transform site feeds both the table and string renderers. - rendering_unindentedfields_cmd_help_GAP flipped to rendering_unindentedfields_cmd_help (expects aligned; both renderers measured). - @cmd -summary: verified no renderer applies an indent transform to -summary (it is only used inline in synopsis "# ..." lines), so -unindentedfields membership for -summary is accepted and vacuously honoured - nothing to gate. This satisfies the acceptance's "-help/-summary honour" clause for -summary. - Blast radius checked before the change: no in-tree definition sets -unindentedfields on a @cmd line, so no existing rendering changed. - define doc for -unindentedfields now states where the option is valid (argument lines and the @cmd directive's -help). - Verified (tclsh 9.0.3): full punk::args suite 175 pass / 1 pre-existing skip / 0 fail; punk::ns suite 53/53 (arg_error consumer); make.tcl modules builds clean. ### 2026-07-12 increment 2: 'i help' alignment via -unindentedfields (punk 0.2.4) - ::punk::helptopic::define_docs now authors its help text at the left margin and declares -unindentedfields {-help} on the generated @cmd line (using increment 1's gate) and on the topic argument line (a gate that existed all along but was never applied here). Verified in punk902z src: the Description block's +12 continuation leak and the topic Help first-line +4 are both gone; 'i help' renders flush end-to-end. punk::ns suite 53/53; make.tcl modules clean. - Decision (user, 2026-07-12): when the constructed-def normalization mechanism lands, define_docs converts from its interim left-margin authoring to indented-plus-normalized authoring - it remains the acceptance's consumer proof as written. Left-margin authoring via -unindentedfields stays a supported style proven by tests (rendering_unindentedfields_arg_help, rendering_unindentedfields_cmd_help), but it is not the preferred style for define_docs itself. ### 2026-07-12 increment 3: -& record continuation (punk::args 0.7.0) - Implemented in private::split_definition_records per the decision recorded in "The -& record-continuation candidate" section above (unquoted trailing token, brace escape, element-count alternative rejected, byte-identical assembly to the backslash equivalent). - First implementation attempt kept a literal backslash-newline in the assembled record and failed: braced definitions never contain backslash-newline by the time they reach the splitter (Tcl pre-collapses it), and downstream record handling assumes the collapsed shape. The collapse-to-single-space rewrite is the correct equivalence and is what landed. - New testsuite recordcontinuation.test (6 tests): backslash-twin parse+render equality (same-length ids so table geometry matches), constructed-def chaining incl. into a multi-line quoted value, braced {-&} escape, mid-line -&, word-suffix abc-&, -& inside still-open braces. - define doc documents -& alongside the backslash continuation paragraph. - Verified (tclsh 9.0.3): full punk::args suite 181 pass / 1 pre-existing skip / 0 fail; full source-tree suite (splitter regression sweep) 815 total, 801 pass, 13 skip, 1 fail = exec-14.3 only (the known pre-existing core-test baseline) - zero regressions. - tclparser considered and rejected for the splitter (user question 2026-07-12): parse command mis-tokenizes the dialect's legitimate unbalanced-bracket ANSI data inside quoted values (info complete gets a throwaway ansistripped copy; parse would need strip+range-remapping), the punk semantics (base-indent line trimming, -&) sit outside Tcl's grammar either way, and a binary dependency in punk::args inverts G-004 for the module that must load everywhere. tclparser remains right for real-script analysis (punk::lib/G-019). Candidate flagged: dev-time lint cross-checking splitter boundaries against parse where the binary is available. Remaining for acceptance: - constructed-definition whole-block normalization opt-in, with rendering_constructed_def_indent_characterization expectations updated and ::punk::helptopic::define_docs converted from interim left-margin authoring to indented-plus-normalized as the consumer proof (user decision 2026-07-12, see increment 2 notes) - defquoting.test container quoting rules promoted into the punk::args::define -help documentation