KQ-2065 · PASTE/FOLD
Paste Fold
A prompt composer that recognises a large paste — a log, a table, a long email, code — and instead of flooding the field spills it in as a sheet of its first lines, then folds it on springs.glide into a labelled block showing its kind, line count and first line, with the caret already after it so you keep typing. Peeking flips the composer to the block's numbered lines with the height gliding, and Trim keeps only the lines you mark (Errors only marks a log's errors in one press); over the character limit the meter turns danger and Send holds. The message is a flow of real textareas and block chips: Left at the start of a run or Backspace there selects the block before it, Space peeks inside, Backspace removes it, and on a phone blocks wrap as full-width chips.
Gaugeworks · import service · run 4412
type, or paste something long · gaugeworks import
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/paste-fold
Props
- foldThreshold
- number
- default 8
- Pastes with at least this many lines fold into a block; shorter ones land as text.
- chipStyle
- "tab" | "card" | "pill"
- default "tab"
- A tab with a kind strip and the first line, a two-line card, or a compact pill.
- accent
- string
- default "var(--accent-bright)"
- Chips, selection, meter and Send — any CSS colour.
- value / defaultValue
- PasteFoldPart[]
- default — / defaultPasteFoldValue
- The message as text runs and folded blocks in order, controlled or initial.
- onValueChange
- (parts: PasteFoldPart[]) => void
- default —
- Fires with the whole message after every edit, fold, trim or removal.
- rules
- PasteFoldRule[]
- default defaultPasteRules
- How pastes are recognised (log, table, email, code), tried in order, each with an optional one-press keep filter for the trim view; anything else folds as Text.
- foldChars
- number
- default 1200
- Pastes with at least this many characters fold too, whatever their line count.
- limit
- number
- default 4000
- The most characters a message may carry, blocks included; past it the meter turns danger and Send holds.
- onPasteDetected
- (block: PasteBlock) => void
- default —
- A paste was folded into a block.
- onExpand
- (block: PasteBlock) => void
- default —
- A block was opened to peek inside.
- onTrim
- (block: PasteBlock, kept: number[]) => void
- default —
- A block was trimmed to the marked lines (indexes into its previous lines).
- onSubmit
- (parts: PasteFoldPart[], text: string) => void
- default —
- Enter or Send: the parts, and the message as plain text with blocks fenced.
- placeholder
- string
- default "Describe the problem, or paste the log…"
- Placeholder for an empty message.
- label
- string
- default "Message"
- The message's accessible name.
- busy
- boolean
- default false
- An answer is being written: Send holds and spins.
- disabled
- boolean
- default false
- Nothing can be typed, pasted or sent.
- ref
- React.Ref<PasteFoldHandle>
- default —
- Imperative handle: paste(text) pastes as a real paste would; focus() puts the caret at the end.
- className
- string
- default —
- Classes for the outer surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| foldThreshold | number | 8 | Pastes with at least this many lines fold into a block; shorter ones land as text. |
| chipStyle | "tab" | "card" | "pill" | "tab" | A tab with a kind strip and the first line, a two-line card, or a compact pill. |
| accent | string | "var(--accent-bright)" | Chips, selection, meter and Send — any CSS colour. |
| value / defaultValue | PasteFoldPart[] | — / defaultPasteFoldValue | The message as text runs and folded blocks in order, controlled or initial. |
| onValueChange | (parts: PasteFoldPart[]) => void | — | Fires with the whole message after every edit, fold, trim or removal. |
| rules | PasteFoldRule[] | defaultPasteRules | How pastes are recognised (log, table, email, code), tried in order, each with an optional one-press keep filter for the trim view; anything else folds as Text. |
| foldChars | number | 1200 | Pastes with at least this many characters fold too, whatever their line count. |
| limit | number | 4000 | The most characters a message may carry, blocks included; past it the meter turns danger and Send holds. |
| onPasteDetected | (block: PasteBlock) => void | — | A paste was folded into a block. |
| onExpand | (block: PasteBlock) => void | — | A block was opened to peek inside. |
| onTrim | (block: PasteBlock, kept: number[]) => void | — | A block was trimmed to the marked lines (indexes into its previous lines). |
| onSubmit | (parts: PasteFoldPart[], text: string) => void | — | Enter or Send: the parts, and the message as plain text with blocks fenced. |
| placeholder | string | "Describe the problem, or paste the log…" | Placeholder for an empty message. |
| label | string | "Message" | The message's accessible name. |
| busy | boolean | false | An answer is being written: Send holds and spins. |
| disabled | boolean | false | Nothing can be typed, pasted or sent. |
| ref | React.Ref<PasteFoldHandle> | — | Imperative handle: paste(text) pastes as a real paste would; focus() puts the caret at the end. |
| className | string | — | Classes for the outer surface. |