feat: label code fences so Vicinae can colour them #10

Merged
adrianbonpin merged 1 commits from feat/code-blocks into prod 2026-10-03 21:37:12 +00:00
Owner

The good news: Vicinae already does this

Nothing needed building. MdCodeBlock.qml renders:

  • a rounded panel on Theme.secondaryBackground (the dark background)
  • a header showing the fence's language, plus a Copy button with "Copied!" feedback
  • syntax colouring, in the theme's mono font, with horizontal scroll

The only missing piece was the language string on the fence.

Why the label matters more than it looks

Vicinae resolves the language with KSyntaxHighlighting:

auto def = repo.definitionForName(language);
if (!def.isValid()) def = repo.definitionForFileName(QStringLiteral("file.") + language);
if (!def.isValid()) return wrapPre(code.toHtmlEscaped());   // label shows, no colouring

So ts resolves through file.ts and bash through its definition name, while an unrecognised string still renders a label but produces no highlighting at all. A wrong label is worse than none.

Changes

  • The default prompt now requires a language on every fence, forbids leaving code inline in a sentence, and forbids padding commands with a leading $ so they stay copy-pasteable.
  • code.ts labels fences the model left bare. Deliberately conservative: it claims bash only on a clear shell signal, json only when the body actually parses, ts only on TypeScript-specific syntax, and leaves plain JavaScript alone rather than guessing.

Verified by rendering

I injected a probe with four fences and read the result back:

Fence Label Colouring
```bash bash ✅ git coloured
bare ``` none ❌ plain white
```ts ts ✅ const / number / length coloured
```terminal terminal ❌ plain white

On terminal: it renders the label you asked for, but costs the colouring, because there is no such KSyntaxHighlighting definition. I used bash for shell content so you get both. If you would rather have the word terminal than the colours, it is a one-line change in code.ts.

I also skipped the ~ / > prompt prefix idea: it would become part of the block content, so the Copy button would hand you a command with a prompt character stuck to the front.

Bug found and fixed

labelCodeFences had an index error in its unlabelled branch — it pushed the wrong line back, duplicating code instead of preserving the opening fence. Caught by its own test before shipping.

Verification

  • tsc --noEmit clean, eslint . clean
  • 156 tests passing across 11 files (was 130 across 10); new code.test.ts covers inference and fence rewriting
  • vici build succeeds
## The good news: Vicinae already does this Nothing needed building. `MdCodeBlock.qml` renders: - a rounded panel on `Theme.secondaryBackground` (the dark background) - a header showing the fence's language, plus a **Copy** button with "Copied!" feedback - syntax colouring, in the theme's mono font, with horizontal scroll The only missing piece was the **language string** on the fence. ## Why the label matters more than it looks Vicinae resolves the language with KSyntaxHighlighting: ```cpp auto def = repo.definitionForName(language); if (!def.isValid()) def = repo.definitionForFileName(QStringLiteral("file.") + language); if (!def.isValid()) return wrapPre(code.toHtmlEscaped()); // label shows, no colouring ``` So `ts` resolves through `file.ts` and `bash` through its definition name, while an unrecognised string still renders a label but produces **no highlighting at all**. A wrong label is worse than none. ## Changes - **The default prompt** now requires a language on every fence, forbids leaving code inline in a sentence, and forbids padding commands with a leading `$` so they stay copy-pasteable. - **`code.ts`** labels fences the model left bare. Deliberately conservative: it claims `bash` only on a clear shell signal, `json` only when the body actually parses, `ts` only on TypeScript-specific syntax, and leaves plain JavaScript alone rather than guessing. ## Verified by rendering I injected a probe with four fences and read the result back: | Fence | Label | Colouring | | --- | --- | --- | | ` ```bash ` | `bash` | ✅ `git` coloured | | bare ` ``` ` | none | ❌ plain white | | ` ```ts ` | `ts` | ✅ `const` / `number` / `length` coloured | | ` ```terminal ` | `terminal` | ❌ plain white | **On `terminal`:** it renders the label you asked for, but costs the colouring, because there is no such KSyntaxHighlighting definition. I used `bash` for shell content so you get both. If you would rather have the word `terminal` than the colours, it is a one-line change in `code.ts`. I also skipped the `~` / `>` prompt prefix idea: it would become part of the block content, so the Copy button would hand you a command with a prompt character stuck to the front. ## Bug found and fixed `labelCodeFences` had an index error in its unlabelled branch — it pushed the wrong line back, duplicating code instead of preserving the opening fence. Caught by its own test before shipping. ## Verification - `tsc --noEmit` clean, `eslint .` clean - **156 tests passing** across 11 files (was 130 across 10); new `code.test.ts` covers inference and fence rewriting - `vici build` succeeds
adrianbonpin added 1 commit 2026-10-03 21:36:40 +00:00
Nothing needed building for the dark code panel: MdCodeBlock.qml already
provides a rounded secondaryBackground panel, a header naming the
language, a Copy button with "Copied!" feedback, and horizontal scroll.
The language string is the only missing piece, and it is also what turns
on highlighting. syntax::highlight resolves it with
definitionForName(language) and then definitionForFileName("file." +
language), so `bash` and `ts` colour correctly while an unrecognised
string such as `terminal` still renders a label but produces no
highlighting at all.

Two changes:

- The default prompt now requires a language on every fence, and forbids
  leaving commands inline in a sentence or padding them with a leading $,
  so commands stay copy-pasteable.
- code.ts labels fences the model left bare. It is deliberately
  conservative, because a wrong label is worse than none: it claims bash
  only on a clear shell signal, json only when the body actually parses,
  ts only on TypeScript-specific syntax, and leaves plain JavaScript
  alone rather than guessing.

Verified by rendering a probe with four fences and reading the result:

  ```bash     -> "bash" label, "git" coloured
  ```        -> no label, no colouring
  ```ts       -> "ts" label, const/number/length coloured
  ```terminal -> "terminal" label, no colouring

`bash` is used over `terminal` for shell commands precisely because
`terminal` costs the colouring.

Also fixes an index bug in labelCodeFences found by its own test: the
unlabelled branch pushed the wrong line back, duplicating code instead of
preserving the opening fence.

typecheck, eslint, 156 tests and vici build all pass.
adrianbonpin merged commit ac2da73669 into prod 2026-10-03 21:37:12 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adrianbonpin/lam-agent-vicinae#10