Skip to content

Multi-line action result values - #3866

Merged
dr-bonez merged 1 commit into
masterfrom
feat/action-result-multiline
Sep 3, 2026
Merged

Multi-line action result values#3866
dr-bonez merged 1 commit into
masterfrom
feat/action-result-multiline

Conversation

@MattDHill

@MattDHill MattDHill commented Aug 28, 2026

Copy link
Copy Markdown
Member

Closes #3810.

The gap

ActionResultValue had two variants and neither could carry multi-line text. Single renders as an <input>, and HTML's value-sanitization algorithm strips CR/LF from a text input's value, so a multi-line value arrived with its lines concatenated. That left message as the only home for a diagnostic report, a generated config file or an exported key block — prose under the title, with no name, no copy affordance, no QR and no download. Action inputs have had ValueSpecTextarea since the start; results had no counterpart, and packagers worked around it by returning the same report twice (Start9-Community/vikunja-startos#6).

The variant

Multiline {
    value: String,
    copyable: Option<bool>,
    qr: Option<bool>,
    masked: Option<bool>,
    filename: Option<String>,
}

A third variant rather than Single { multiline: bool }: the widget differs, the sizing differs, and the flags mean different things per widget — one component branching across two layouts is the shape that produced the group-copy bug in #3792. It also keeps Single's contract, and its doc comments, true.

Every flag is optional and defaults to false, the contract #3878 set for single's flags; launchable stays on single, where the value is a URL. filename is what separates "here is some text" from "here is a file". Set it and the value is offered as a download under that name; omit it and it is text on the screen.

The widget

StartTunnel's device-config dialog minus its help infrastructure: a read-only textarea tuiTextarea inside a tui-textfield, sized to content between 3 and 16 rows, monospace so aligned columns survive, masked by blur with a reveal button, and a QR opened through the same dialog action-success-member already used. Name and description sit above the field as a tuiTitle / tuiSubtitle pair, the way about.component.ts groups a modal item with its caption.

Two things the tall textarea exposed that a one-line input had been hiding:

  • Focus stealing. TuiTextfieldComponent projects everything that isn't the input or label into a <span class="t-content" (pointerdown)="input()?.nativeElement?.focus()">. That handler has no .self, so clicking any in-field icon focuses the input — imperceptible for an <input>, a jarring scroll for a 16-row textarea below the fold. Every in-field control now carries (pointerdown.stop). Copy also moved from tuiCopy to CopyService, since tuiCopy copies by calling .select() on the native element, which scrolls for the same reason; CopyService is what both sibling components and the About modal already use.
  • Width. The result dialog opens at size l when the result carries a multiline value at any depth, so a report's columns don't wrap mid-row.

Elsewhere

  • fmt_rec's Single and Multiline arms are one or-pattern that indents every line of a value, so start-cli prints the new variant the way it prints the old one.
  • --tui-typography-family-code is defined in the theme sheet alongside the existing family overrides. Taiga's default resolves to 'JetBrains Mono', which this app doesn't load, so it would have silently fallen back to the sans stack and defeated the aligned columns.
  • Mock fixtures for all three shapes, on Bitcoin Core: Show Report (copy + download), Show Device Config (QR + masked + download), Show Mixed Group (a group mixing single and three multiline members).
  • actions.md § Result Types now documents all three variants and what each takes; changelog entries in 0.4.0.2 and SDK 2.0.10. No new i18n keys — Show QR, Scan this QR, Reveal, Reveal/Hide and Download all already existed.

One thing to check

The two regenerated bindings are hand-written. make start-core-ts-bindings compiles start-core at opt-level = 3 with full debuginfo, which does not fit in this machine's docker budget. ActionResultValue.ts and ActionResultMember.ts were written by hand from the existing generated output, including #3878's optional-field form. The Generated Artifacts job uploads what it regenerates, so if start-core-ts-bindings-check fails, the run's artifact replaces them.

🤖 Generated with Claude Code

`ActionResultValue` had two variants and neither could carry multi-line text.
`Single` renders as an `<input>`, and HTML's value-sanitization algorithm strips
CR/LF from a text input's value, so a multi-line `value` arrived with its lines
concatenated. That left `message` as the only home for a diagnostic report, a
generated config file or an exported key block — prose under the title, with no
name, no copy affordance, no QR and no download. Action *inputs* have had
`ValueSpecTextarea` since the start; results had no counterpart, and packagers
worked around the gap by returning the same report twice.

`Multiline` is a third variant rather than `Single { multiline: bool }`: the
widget differs, the sizing differs, and the flags mean different things per
widget. `filename` is what separates "here is some text" from "here is a file" —
set it and the value is offered as a download, leave it null and it is text on
the screen.

The component is StartTunnel's device-config dialog minus its help
infrastructure: a read-only `textarea tuiTextarea` inside a `tui-textfield`,
sized to content between 3 and 16 rows, monospace so aligned columns survive,
masked by blur with a reveal button, and a QR opened through the same dialog
`action-success-member` already used. Name and description sit above the field
as a `tuiTitle`/`tuiSubtitle` pair, the way the About modal groups an item with
its caption.

Two things the tall textarea exposed that a one-line input hid. Every in-field
control carries `(pointerdown.stop)`, because `TuiTextfieldComponent` projects
them into a `.t-content` span that focuses the input on any pointerdown that
reaches it — harmless for an `<input>`, a jarring scroll for a 16-row textarea.
And the result dialog opens at size `l` when the result carries a multiline
value at any depth, so a report's columns do not wrap mid-row.

`fmt_rec`'s `Single` and `Multiline` arms are one or-pattern that indents every
line of a value, so `start-cli` prints the new variant like the old one.
`--tui-typography-family-code` is defined in the theme sheet because Taiga's
default resolves to 'JetBrains Mono', which this app does not load, and would
have silently fallen back to the sans stack.

Closes #3810

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MattDHill
MattDHill force-pushed the feat/action-result-multiline branch from 4f0bd30 to d2645f6 Compare September 3, 2026 13:40
@MattDHill
MattDHill changed the base branch from feat/known-registries to master September 3, 2026 13:41
@dr-bonez
dr-bonez merged commit f6fd333 into master Sep 3, 2026
33 of 51 checks passed
@dr-bonez
dr-bonez deleted the feat/action-result-multiline branch September 3, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Action results have no multi-line member type — Single renders as an <input>, which strips newlines (action.rs:190)

2 participants