Repository navigation
L0: model-written text in text slots, strict actions, in-card chat - #53
Merged
Merged
Conversation
Cards now carry the model's own words (summaries, drafts, reports) and an
in-card chat with the app's agent. Profile §4.2 and §5.15.
Model text (model-copy, model-written host fields such as sys.digest's
summary/text/label and sys.chat's text, and any text state such text is
written into) is:
- allowed in a catalogued text slot (TextHero/Title/Body/Row/Eyebrow/
Caption, Band, Bubble, ChatEntry, Field .text), carrying
ValueOrigin::Model and marked AI-written by every lowering:
kit `l0_ai_text(...)`, makepad ` l0_ai: true`, DSL `ai: 1`, and
`ai_written(&UiNode)`;
- allowed in `set(path)` into a `text` state (a draft), which is then model
text to a fixed point; dispatch now resolves `set(copy.x)` from the card;
- refused everywhere else, default deny: payloads, guards, loop keys, source
arguments, control labels, component props, Kit args, state initials,
non-text states and host stores.
P1 fix: makepad::lower built Field targets by splicing the typed text into
a JSON string unescaped, so typed text could redirect the event. on_return
(and now on_change) emit a serialized routing head plus sys.json_string(t).
Chat: `sys.chat(app, thread, fields)` (host-answered, scoped like
sys.digest, append-only) answering entries {id, role, text, at}, and the
`ChatEntry(text: m.text, role: m.role)` role, which must read text and role
off the same sys.chat row; `model` entries are marked AI-written.
Docs: ui-profile-l0.md §4 table, §4.2, §5.14 note, §5.15; constructor TOML.
Tests: tests/ai_text.rs and fixtures/chat.card; two profile tests updated
to the new rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Design decision (2026-10-01): L0 cards are no longer only real-time generated layouts. The model fills them with its own words (summaries, drafts, research reports), and people chat with the app's agent inside a card. Until now the checker refused model-written text anywhere on screen: in
check_pathfor rendering, and incheck_event_batchforset(copy.x). On this main those checks were atlib.rs:5720and:4447, not at the5619/4412the brief cited.The new rules (profile §4.2, §5.15)
What counts as model text: a
model-copydeclaration, a model-written host field (catalog::MODEL_TEXT:sys.digestsummary/text/label,sys.chattext), or atextstate that such text was written into (a draft).Words: allowed in a text slot, marked AI-written.
catalog::TEXT_SLOTS) are thetextargument of TextHero, TextTitle, TextBody, TextRow, TextEyebrow, TextCaption, Band, Bubble, ChatEntry and Field. They do not include chip, tile and tab labels, avatars, glyphs, suffixes,Kitargs or placeholders.ValueOrigin::Model, which survivescombine.ai_written(&UiNode)decides the mark, and all three lowerings emit it:kit::lowerwraps the call inl0_ai_text(…), innermost (Field included).makepad::loweraddsl0_ai: true.lower_dsladdsai: 1.set(path)model text into atextstate (a draft). That state then counts as model text, including through chainedsets. Dispatch now resolvesset(copy.x)from the card's own copy, which it previously looked up in the data blob, where it never is.Actions: strict, default deny.
check_pathrefuses model text in every position except a catalogued text slot. That covers payloads (value:), guards, loop keys, source arguments (includingstate.<draft>), control labels, component props,Kitargs, state initials, writes into non-text states, and writes into host stores (sys.link,sys.watchlist, …).P1 fix:
makepad::lowerbuilt a Field'son_returntarget ashead + t + tailaround a"$$"hole without escapingt. Typing","e":"drop","v":"therefore redirected the event, because a JSON parser keeps the laste. Bothon_returnand (newly lowered)on_changenow emit a serialized routing head plussys.json_string(t). This is the constructionkit::tap_targetalready uses.Chat:
source convo sys.chat(app, thread, fields)is host-answered only (novm_call). It is scoped likesys.digest, with a literalappand athreadid or path, and answersentries {id, role, text, at}plusstatusandcount.roleis one ofuser,modelorhost.append. The payload is what the user committed in aField. The host appends the agent's reply, and a card can never write amodelentry.ChatEntry(text: m.text, role: m.role)draws a bubble on the side its role says. Both arguments must read the samesys.chatrow, so a card cannot present model text as the user's. Onlymodelentries get the AI-written mark.Tests
tests/ai_text.rs(12 tests) andfixtures/chat.card. They cover:l0:{…}," + sys.shell(…) + ",javascript:links, NUL,set:verbs) staying inert literalscargo test -p octoscript-ui-l0: 310 passed, 0 failed.cargo clippy -p octoscript-ui-l0 --all-targets -- -D warnings: clean.cargo check -p octoscript-core --all-targets: clean.Downstream changes needed (not in this PR)
Octoscript-Makepad (kit and renderer)
_kit.octoscriptneedsfn l0_ai_text(node) { node.ai = 1 return node }. Until it exists, a card containing model text will not evaluate.octoscript-nodeAttrsandoctoscript-rendereval.rsneed to readai, and the renderer needs to draw a visible AI-written treatment.l0_ai: truethe way it doesl0_event:.ChatEntryreuses the existingl0_bubble_me,l0_bubble_themand*_longfunctions, so it needs no new kit role.OctoSense (host)
sys.chatfor the publishing app only:appagainst the publisher;appendCollectionWrite, record auserentry, run the app's agent, and append its reply as amodelentry (or ahostnotice);modelentry.event_payload_origin/dispatch_reporting_with_origin, whichfield_target_expralready does structurally.apps/appcard/a2app-l0/framework/catalog.mdwithgen-catalog.py. Running it locally addsChatEntryandsys.chat, and also the earliersys.digest,sys.datasetandsys.news_*entries that the current copy is missing.framework/l0.md(line 140 says model-copy "may not be rendered") anddocs/LEDGER-ARCHITECTURE.mdto the new rule.l0_ai_textlands.🤖 Generated with Claude Code