Add agent-to-app rendering infrastructure#127
Open
ZhangHanDong wants to merge 22 commits into
Open
Conversation
Master spec defines the system-level invariants for delivering bot-authored mini-apps into the Robrix timeline: protocol envelope composition (`org.octos.app` + reused `org.octos.actions`, not merged), host identity keyed on `(room_id, event_id)` not on agent-provided semantic IDs, `m.replace` immutability aligned with Phase 4c / Phase 5, the L1/L2a/L2b/L3 layering contract, the `RoomScreen`/`PortalList`/`TimelineUiState` lifecycle integration for L3 hosts, and the type registry whitelist. Roadmap document is cross-reviewed by Codex across four review rounds and cites upstream Makepad source (`splash.rs`, `button.rs`, `widget.rs`, `widget_tree.rs`) plus the existing Robrix Phase 4c action-button wiring as primary-source evidence that L1 and L2a are immediately feasible and that L2b/L3 need only a 30-minute micro-PoC rather than a full spike. `agent-spec lint specs/task-agent-to-app-system.spec.md --min-score 0.7` returns Quality 100% with 17 scenarios covering protocol routing, host identity, envelope/actions immutability, layering, lifecycle integration, and security/validation invariants. No code is touched by this commit. L1/L2a/L2b/L3 sub-specs derive from this master contract in subsequent commits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Derives from the agent-to-app master spec to define the first concrete
mini-app: a presentation-only weather card routed through the new
`org.octos.app` type registry, not through the `org.octos.splash_card`
raw-string backdoor.
Contract details:
- JSON schema: location / temp_c / condition required; feels_like_c /
humidity / wind_kph / updated_at / forecast optional; forecast capped
at 7 entries; schema versioned.
- Factory interface: `init(initial_state) -> RenderedApp` + `render(state,
app_language) -> String`. The render function takes `AppLanguage`
explicitly so label localisation does not leak through global state.
- Splash output constraints (per makepad-2.0-splash skill for the Canvas
eval path): dot-path inline properties, `draw_bg.radius` (not
`border_radius`), explicit `Inset{}` and `Align{}` types, trailing-dot
form for whole-number float literals only, no `ScrollYView`, no
`show_bg: true` on pre-styled views.
- Validation: missing required fields and out-of-range numerics fail
closed to plain text with a warning; unknown `condition` enum values do
NOT fail closed and instead map to "sunny" with a warning; long
locations truncate at 64 grapheme clusters with a U+2026 ellipsis;
forecast >7 truncates with a warning.
- Immutability inherited from master spec: the renderer reads the original
event content only, never `m.new_content`.
14 scenarios cover registry routing, missing/invalid field handling,
grapheme-cluster truncation, Splash-safe string escaping, Canvas
eval-path syntax verification, coexistence with `org.octos.actions`, the
`m.replace` immutability rule, priority over raw `splash_card`, and i18n
label resolution against the current app language.
`agent-spec lint specs/task-agent-to-app-l1-weather-card.spec.md
--min-score 0.7` returns Quality 100%.
Codex code-level review of this spec caught and verified four fixes before
this commit: (1) `render` signature now takes `app_language` explicitly to
avoid global language state, (2) `Allowed Changes` now includes
`src/home/mod.rs` so the new `app_registry` submodule can be re-exported,
(3) float syntax rule split into whole-number-float (trailing dot) vs
fractional-float (unchanged) categories to resolve a self-contradiction,
(4) corrected the `splash_card` widget slot line reference in
`room_screen.rs` from the stale `:1410` to the actual `:1968`.
No code is touched by this commit. L1 code implementation begins in a
follow-up commit after user testing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First concrete agent-to-app mini-app shipping against the master spec
`specs/task-agent-to-app-system.spec.md` and the L1 sub-spec
`specs/task-agent-to-app-l1-weather-card.spec.md`.
Changes:
- New `src/home/app_registry/` module with an `AppFactory` trait and a
once-initialised type registry. L1-level factories only need to
implement `init + render(&state, AppLanguage) -> String`; higher
layers (L2b in-card controls, L3 stateful hosts) will extend the
trait without breaking this commit.
- New `weather` type (`src/home/app_registry/weather.rs`) implementing
the full L1 weather contract: location / temp_c / condition
required; feels_like_c / humidity / wind_kph / updated_at / forecast
optional; six-way condition enum (sunny / cloudy / rainy / snowy /
stormy / foggy); grapheme-cluster truncation for overly long
locations; forecast cap of 7 entries; Splash-safe string escape
helper. Unknown condition values fall back to `sunny` with a warning
rather than failing the whole card.
- Render function produces Canvas eval-path Splash DSL following the
`makepad-2.0-splash` skill's syntax rules: dot-path inline
properties, `draw_bg.radius` (not `border_radius`), explicit
`Inset{}` and `Align{}` types, trailing-dot form for whole-number
float literals only, no `ScrollYView`, no `show_bg: true` on
pre-styled views. A dedicated unit test
(`render_output_obeys_canvas_eval_syntax_requirements`) enforces
each of these invariants so future edits cannot silently regress
the eval-path syntax.
- New `org.octos.app` parallel branch in `src/home/room_screen.rs` at
the content parsing point where `org.octos.splash_card` is
currently read. The app registry path takes priority when both
custom fields are present. Event content is read via
`original_event_content_json` rather than
`latest_effective_event_content_json` to enforce the master spec's
m.replace immutability rule: edits to a message carrying an
`org.octos.app` envelope do not mutate the rendered card.
- Five new i18n keys (`agent_to_app.weather.*`) for feels_like /
humidity / wind / forecast / updated_at_prefix, in both EN and
zh-CN. The render function resolves labels against the current
`AppLanguage` passed in explicitly — no global language state.
- 11 unit tests under `home::app_registry::weather::tests` cover
valid payload success, missing-required-field fail-closed,
out-of-range temperature fail-closed, unknown-condition soft
fallback, optional-field absence, long-location grapheme-cluster
truncation with ellipsis, forecast truncation, Splash-safe escape,
Splash DSL injection-attempt escape, Canvas eval syntax
conformance, and i18n label resolution.
Verified end-to-end by sending a weather event as the appservice
ghost user `@octosbot:127.0.0.1:8128` into the local octos-public
room and observing the native GPU-rendered card (orange background,
CJK location, ☀ symbol, 22° temperature, Feels like / Humidity /
Wind column, three forecast chips with sunny / cloudy / rainy
symbols) in the Robrix timeline. `cargo test home::app_registry::weather`
passes 11/11. `RUSTFLAGS="-D warnings" cargo clippy --workspace
--all-features` is clean.
What this commit does NOT include (follow-up work, deliberately
scoped out per the master spec's producer/consumer boundary):
- OctOS-side producer: bot agent does not yet emit `org.octos.app`
envelopes in response to natural-language weather requests. That
belongs in the OctOS repo and will be tracked in a separate spec
(`task-octos-agent-app-envelope-producer`).
- L2a external refresh button, L2b in-card controls, L3 stateful
hosts: separate sub-specs and commits.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ignup) (#114) * feat(register): scaffold src/register/ module Add empty register module with mod.rs, register_screen.rs, register_status_modal.rs, validation.rs. Register the module in src/lib.rs and wire the script_mod aggregator per the login/mod.rs pattern. Part of specs/task-register-flow.spec.md Phase 1. * feat(register): homeserver URL normalizer + tests Accept bare hostname (prepend https://), strip trailing slash, reject non-http(s) schemes and empty input. 8 unit tests cover the edge cases. * feat(register): add RegisterAction + HsCapabilities types Introduce the data model used across Phase 1-5: - HsCapabilities with is_mas_native_oidc / registration_enabled / uiaa_probe / sso_providers fields matching the spec - RegisterMode enum (MasWebOnly / Uiaa / Disabled) derived via HsCapabilities::mode() — MAS wins over UIAA per element-web rule - RegisterAction with NavigateToLogin / CapabilitiesDiscovered / DiscoveryFailed variants for Phase 1 + None default * feat(login): dispatch NavigateToRegister for Sign Up click Add LoginAction::NavigateToRegister variant. Replace set_signup_mode(true) call with Cx::post_action dispatch so the main App can route to the new RegisterScreen. The signup-mode rendering code is removed in a later task. * feat(register): capability discovery + RegisterScreen UI Implement MatrixRequest::DiscoverHomeserverCapabilities handler that probes .well-known, /versions, /v3/login, and empty POST /register to build HsCapabilities. Results post back as RegisterAction::CapabilitiesDiscovered / DiscoveryFailed. RegisterScreen widget renders the homeserver input, Next button, and three-state status area (MAS / UIAA / Disabled / errors). The full wizard body is added in Phase 2+. * refactor(login): remove signup mode residue Delete confirm_password input, mode-toggle state (signup_mode field, set_signup_mode, sync_mode_texts), and the signup submit branch in handle_actions. The "Sign up here" button (DSL id mode_toggle_button) keeps its text, position and style; its click now posts LoginAction::NavigateToRegister (wired in f40871d) — the navigation handler lands in a follow-up commit (Task 8). Registration logic moves wholesale to src/register/; see specs/task-register-flow.spec.md. * feat(app): wire login <-> register navigation Handle LoginAction::NavigateToRegister and RegisterAction::NavigateToLogin to toggle visibility between login_screen_view and the new register_screen_view. Both screens live as siblings under the overlay_container; default visibility stays on LoginScreen. auth_ui_state and update_login_visibility are intentionally left untouched — Phase 2+ will layer auth-state machinery on top of this minimal inline toggle. * fix(register): theme tokens + MSC2965 unstable auth key Task 9 testing found two bugs: 1. register_screen.rs hard-coded dark-theme colors (#x1F2124 bg, #xF1F2F3 text) conflicted with the project's light palette; combined with `draw_bg:` (replace) instead of `draw_bg +:` (merge, Pitfall #44) the shader silently fell back to transparent and text appeared washed out. Replace all hex colors with the shared COLOR_SECONDARY / COLOR_TEXT tokens and use TITLE_TEXT / REGULAR_TEXT styles, matching login_screen conventions. 2. MAS detection only looked at the stable .well-known key `m.authentication.issuer`. matrix.org still serves the unstable key `org.matrix.msc2965.authentication.issuer`, so it was mis-classified as non-MAS and fell through to the "registration disabled" branch. Accept both keys (element-web does the same). * fix(register): Makepad 2.0 DSL syntax errors Runtime DSL errors surfaced at `cargo run`: 1. register_status_modal.rs used Makepad 1.x live_design! form `pub RegisterStatusModal := {{RegisterStatusModal}} View {...}`. In script_mod! the 2.0 form is `mod.widgets.RegisterStatusModal = #(RegisterStatusModal::register_widget(vm)) {...}` (Pitfall #43). The 1.x form emitted "variable pub not found in scope" + "variable RegisterStatusModal not found in scope". 2. register_screen.rs set `empty_message:` on a RobrixTextInput. The correct property is `empty_text:` (the error helper suggested it exactly). Symptom: the homeserver input was never created because its property bag failed to bind. Both failures pass `cargo build` because script_mod! bodies are parsed at widget init by the Makepad script VM, not by rustc. Caught when running the app. * feat(register): HsCapabilities carries mas_account_url Phase 2 groundwork. HsCapabilities grows a new Option<String> field populated from the same .well-known probe Phase 1 already runs, preferring the explicit `account` field and falling back to `<issuer>/account/` when absent (alvin.meldry.com currently omits the field; matrix.org provides it). No extra HTTP round trips. The URL is consumed in the next commit by RegisterScreen to launch the system browser. * feat(register): open system browser for MAS signup When CapabilitiesDiscovered reports MasWebOnly, call robius_open::Uri::new(&url).open() on the mas_account_url captured in the previous commit, then replace the status text with a user-facing instruction: complete signup in the browser, return, hit Back to Login, sign in with the new credentials. Handles three paths: - browser opens cleanly -> instruction text - robius_open errors -> fallback text with the URL for copy - mas_account_url None -> defensive message ("advertises but no signup URL was found") Simple-version scope: no OAuth callback, no token exchange, no Dynamic Client Registration. Element Desktop's full callback flow is Phase 2.5 if we decide to ship it. With this commit the alvin.meldry.com / matrix.org paths are end-to-end usable: user completes signup in the browser and returns to log in via the existing password flow. * fix(register): use <issuer>/register for MAS signup entry The previous commit opened <issuer>/account/, which is MSC2965's account-management URI and requires an authenticated session — hitting it without a cookie loops between /account/ and /login (verified in incognito against alvin.meldry.com). MAS exposes the direct self-registration form at <issuer>/register; confirmed to render a proper "Create account" page with username/email/phone/ password fields on alvin.meldry.com. Rename the field from mas_account_url to mas_signup_url to match the real semantics, and drop the `account` field lookup from discovery — we do not use it and keeping it as a fallback only invited this bug. Future account-management features in later phases can add it back when there is a real consumer. * refactor(register): mirror LoginScreen card layout exactly Align RegisterScreen's outer shell with origin/main LoginScreen structure: set_type_default + SolidView base, Overlay root with centered alignment, ScrollYView with hidden scrollbar, RoundedView card wrapper with 50/50 margin, inner column with spacing 15.0. Logo references the shared mod.widgets.IMG_APP_LOGO token (registered by login_screen.rs script_mod which runs first in app.rs:1549). * docs(register): Phase 1+2 plans and task-register-flow spec Retroactively commit the planning documents that guided the Phase 1+2 implementation already merged into this branch. These were written during PR#114 development but never checked in.
- Add oidc_login module: worker + error taxonomy + MatrixRequest variants - Wire LoginScreen MAS branch end-to-end (probe → OIDC flow) - Persist OAuth sessions in matrix_state, save on TokensRefreshed - Dispatch server logout via client.auth_api() in logout_state_machine - Add should_probe_homeserver predicate (TDD-driven) - Share homeserver capability state between login and register flows - Tighten back navigation and probe proxy handling between flows - Add OIDC MAS i18n strings (en + zh-CN)
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.
Summary
Verification
Notes