Skip to content

L0: l0_ai_text kit stamp and the AI-written mark (for Octoscript #53) - #68

Merged
ymote merged 2 commits into
mainfrom
feat/l0-ai-text
Oct 2, 2026
Merged

ymote merged 2 commits into
mainfrom
feat/l0-ai-text

Conversation

@ymote

@ymote ymote commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

Why

OctoSense-org/OctoScript#53 lets L0 cards show text the model wrote (a digest summary, a draft, the agent's side of an in-card chat), marked AI-written. Its three lowerings emit the mark, but the kit side was missing: kit::lower wraps model text in l0_ai_text(…), which did not exist, so a card holding model text would not evaluate. This PR adds the kit function, reads the stamp, and draws the mark.

What changes

  • Kit (components/l0/_kit.octoscript): adds fn l0_ai_text(node) { node.ai = 1 return node }. Like l0_live, it only stamps the node.
  • Model (octoscript-node): adds Attrs::ai. A new module, octoscript_node::ai, holds the treatment as a UiNode → UiNode rewrite (expand_ai_marks). Any backend can run the same rewrite, so they all draw the same mark. octoscript-render re-exports it.
  • Evaluator (octoscript-render/src/eval.rs): reads ai.
  • Renderer (octoscript-makepad): to_makepad_ui and to_makepad_l0_ui[_with_events] expand the marks before the ink pass. The beauty inspector JSON reports ai.

The mark

It follows the "AI SUMMARY" eyebrow from the OctoSense mail MVP (#261): a small sparkle and an AI eyebrow drawn above the words. Details:

  • Glyph: Font Awesome solid wand-magic-sparkles (U+E2CA), drawn in the icon face. The ✦ character (U+2726) is not in any text face the kit ships (Roboto, NotoSans, Inter, LXGW, NotoColorEmoji), so it would render as tofu.
  • Colour: the words' own ink at 72% alpha, so it fits every palette without adding a colour. A test runs it across all 11 palettes.
  • Size: about 0.7× the body, clamped to 9–12pt, weight 600, slightly tracked.
  • Placement: the mark sits next to the first text of the stamped node, not around the node. A chat bubble is stamped on its outer column and a band on its filled card. Placed this way, the mark stays on the same fill as the words, so it never ends up as ground-coloured ink on the ground.
  • The words themselves: role, size, face and colour are unchanged.

Plain text only

Model text stays the text: of a plain makepad Label, emitted as an escaped string literal. There is no Markdown, HTML, TextFlow or link widget, and no autolinking. Tests check that markdown, javascript: links, HTML, an injected on_click, and l0:{…} targets all stay inert literals.

l0_ai: true (the Makepad lowering)

No change is needed. makepad's #[derive(Script)] apply reads only declared fields by name (value_for_apply(value, id!(field))), so an undeclared key on a widget object is never read. l0_ai: true is ignored exactly as l0_event:/l0_key: already are. Nothing in this repo consumes makepad::lower's output. The mark the user sees comes from the kit path above.

ChatEntry

Per #53, ChatEntry lowers to the existing bubbles: user → l0_bubble_me, model → l0_ai_text(l0_bubble_them(…)), host → l0_bubble_them, plus the *_long forms. A test renders all three roles. The user's bubble is right-aligned on the accent, the agent's and host's are left-aligned on the panel fill, and only the model's bubble is marked. The mark sits inside its bubble.

Tests

  • crates/octoscript-node/src/ai.rs: 4 unit tests. They cover: untouched when unstamped; mark above the words in the same ink, quieter; idempotent; inside a bubble; text kept verbatim.

  • crates/octoscript-makepad/tests/l0_ai_text.rs: 9 tests against the real kit. The kit calls are copied from the shapes Repin makepad to main f08eec53 (terminal draws Chinese text) #53's tests/ai_text.rs asserts. They cover:

    • stamping
    • every markable role, including Field and Band
    • mark order, ink and icon face
    • all palettes
    • plain-label and injection checks
    • an AI draft in a Field that stays editable
    • the three-role chat transcript
    • an unmarked card that stays unchanged
  • These tests are independent of the pinned Octoscript revision. I also ran them against Repin makepad to main f08eec53 (terminal draws Chinese text) #53's head (3ca709c), along with a scratch end-to-end test (l0::prepare of Repin makepad to main f08eec53 (terminal draws Chinese text) #53's summary card and chat.card in 4 moods), and they pass.

  • cargo test --workspace: all green (octoscript-makepad 115 passed, 8 ignored as before).

  • cargo clippy -p octoscript-node -p octoscript-render -p octoscript-makepad --all-targets -- -D warnings: no new findings. The crate already failed on main because of 3 findings in code this PR does not touch, so this run is not fully clean:

    • clippy::needless_borrow, l0.rs:98
    • clippy::erasing_op, material.rs:672
    • an unused build_with_sys_stub, tests/l0_cards.rs

    With those three allowed, it is clean.

Downstream (OctoSense)

  • Repin Octoscript-Makepad (octoscript-makepad and octoscript-node revs in Cargo.toml, and native-runtime.lock.json) to this PR's merge, together with Octoscript (octoscript-ui-l0 and friends) to Repin makepad to main f08eec53 (terminal draws Chinese text) #53's merge. The .sources checkout supplies _kit.octoscript through OCTOSENSE_WORKSPACE. This repo's own octoscript-ui-l0 pin (Cargo.toml, runtime.json) also moves to Repin makepad to main f08eec53 (terminal draws Chinese text) #53 once that merges.
  • apps/appcard/app/app/src/app/l0_eval.rs builds Attrs { … } exhaustively, so it will stop compiling until it adds ai: int_prop(vm, value, id!(ai)),.
  • l0_widgets::to_dsl (the device renderer) must call octoscript_node::ai::expand_ai_marks(&mut tree) on a clone before emitting. Otherwise the stamp is read but nothing is drawn. glance_card and phone already go through octoscript_makepad::to_makepad_*, which do this.

🤖 Generated with Claude Code

Octoscript #53 lets L0 cards show model-written text and lowers it as
`l0_ai_text(node)`. Add the kit function (stamps `ai: 1`), read `ai`
in octoscript-node/octoscript-render, and draw the mark: a sparkle in
the icon face plus an `AI` eyebrow above the words, in their own ink a
step quieter, placed beside the first text so it stays on the same
fill (bubble, band). The rewrite lives in octoscript_node::ai so every
backend draws the same mark; octoscript-makepad runs it before emit.

Model text stays a plain Label literal: no markup, no autolinking.
` l0_ai: true` from makepad::lower needs no change: makepad's Script
apply reads declared fields only, so it is ignored like `l0_event:`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OctoScript#53, the L0 side of model-written text and the in-card chat,
is on main as 5991dfae: the runtime pin (runtime.json) and the
octoscript-ui-l0 git rev move to it, so this kit stamp and the Octoscript
lowerings that call it ship together.

Checked: cargo test -p octoscript-makepad -p octoscript-node, with the
l0_ai_text tests, against Octoscript at 5991dfae.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ymote
ymote merged commit 8f103d0 into main Oct 2, 2026
ymote added a commit to OctoSense-org/OctoSense that referenced this pull request Oct 2, 2026
…0c (L0 AI text and chat merged)

The two changes this pull request builds against are on their mains:
OctoSense-org/OctoScript#53 (5991dfae, model-written text in text slots,
strict actions, in-card chat) and OctoSense-org/OctoScript-Makepad#68
(8f103d0c, the l0_ai_text kit stamp and the AI-written mark; its
runtime.json pins Octoscript 5991dfae and Makepad e29a0eaa, unchanged).
native-runtime.lock.json (here and AppCard's) and the root manifest's
Octoscript and Octoscript-Makepad revs move to them; the crates resolve
from .sources through [patch], so Cargo.lock does not change.

Checked: python3 tools/setup.py --update, then --check --cargo; python3
tools/native_apps.py --check; cargo check -p octosense; cargo test -p
octosense-shell --features app-hub --lib (715 passed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant