pi-browser-taskbar-rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +22 -0
- data/LICENSE +21 -0
- data/README.md +110 -0
- data/contract/docs/index.md +381 -0
- data/contract/traceability.json +93 -0
- data/contract/traceability.md +36 -0
- data/docs/accessibility-acceptance.md +29 -0
- data/docs/security.md +51 -0
- data/docs/troubleshooting.md +50 -0
- data/lib/generators/pi_browser_taskbar/install_generator.rb +439 -0
- data/lib/pi/browser/taskbar/rails/assets/pi_browser_taskbar.css +2 -0
- data/lib/pi/browser/taskbar/rails/assets/pi_browser_taskbar.js +1295 -0
- data/lib/pi/browser/taskbar/rails/broker.rb +941 -0
- data/lib/pi/browser/taskbar/rails/broker_launcher.rb +19 -0
- data/lib/pi/browser/taskbar/rails/engine.rb +195 -0
- data/lib/pi/browser/taskbar/rails/routes.rb +11 -0
- data/lib/pi/browser/taskbar/rails/task.rb +422 -0
- data/lib/pi/browser/taskbar/rails/version.rb +11 -0
- data/lib/pi/browser/taskbar/rails.rb +196 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e3c308da05c9c6e2e26a77a466a549454f8444b128b884596f882e3f69c3c03b
|
|
4
|
+
data.tar.gz: 12a53b0f9467166cc976942baf5ad654378f3f9a26b097f7f8962b7f94fdbb36
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f723429595fd26ca8142908255baced015e764f114caa986468d1344332eb5063b9cd1084553bf988a32d0a2f53be7a66a411b7eec412619188c6e4bbe1d617b
|
|
7
|
+
data.tar.gz: bf76fe4cef80f09161b45ef674a77e30c1a87d77bea2218a9c5163866642ade465264bf2324f5a82af3ec9d69ecf0440e4a3eb5851a7372c64f2c7ed2fa2d694
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Rails adapter changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - Unreleased
|
|
4
|
+
|
|
5
|
+
- Add the development-only isolated engine, native CSRF/access boundary, ERB installer, and package-served Browser Client.
|
|
6
|
+
- Add the external checkout-scoped broker with exclusive election, verified Unix-socket identity, fake-Pi task completion, and atomic admission.
|
|
7
|
+
- Add bounded structural page capture and independent normalized-context validation.
|
|
8
|
+
- Add up to eight ordered advisory focus marks with stable selectors and fair bounded detail.
|
|
9
|
+
- Attach conservative project-relative ERB template hints from Rails' native rendered-template annotations.
|
|
10
|
+
- Add idempotent task cancellation through the external broker with stable HTTP errors and settled terminal state.
|
|
11
|
+
- Add confirmed in-process session reset through the broker with retained-state rejection and process-recovery fallback.
|
|
12
|
+
- Report Pi progress, bounded UTF-8 output, cancelled extension dialogs, and safe recoverable protocol failures.
|
|
13
|
+
- Recover from task and abort deadlines, startup failures, and process exits with bounded process-tree replacement.
|
|
14
|
+
- Preserve one verified checkout-scoped broker across Rails reload, forked/clustered/phased Puma processes, and concurrent servers, with idle-only grace and graceful process-tree cleanup.
|
|
15
|
+
- Reconcile canonical state across tabs with bounded polling recovery and preserve one taskbar host across full and Turbo navigation without stale marks.
|
|
16
|
+
- Enforce startup-fixed server configuration and fail-closed native development activation.
|
|
17
|
+
- Secure exact remote-host access, native CSRF responses, filtered diagnostics, and persistent trusted-network HTTP warnings.
|
|
18
|
+
- Make Rails installation atomically preflighted, checksummed, explicitly selectable, mounted before host fallbacks, idempotently updatable, and all-or-nothing reversible through Rails' native inverse generator.
|
|
19
|
+
- Complete the Shadow DOM Corner composer with stable lifecycle text, native accessible controls, one live region, keyboard marking and focus return, narrow/200% reflow, and reduced-motion behavior.
|
|
20
|
+
- Verify every stable Rails minor from 7.1 through 8.1 at its minimum Ruby, plus newest Rails on newest MRI, in generated artifact-installed applications and supported Puma modes.
|
|
21
|
+
- Run artifact-isolated Chromium, Firefox, and WebKit Turbo/lifecycle/keyboard flows with taskbar-owned automated accessibility evidence.
|
|
22
|
+
- Enforce lockstep versions, deterministic artifact bytes, strict package contents, clean artifact-installed examples, manual-evidence prerequisites, checksums, and source/workflow provenance before release preparation.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dave Lens
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Pi Browser Taskbar Rails
|
|
2
|
+
|
|
3
|
+
Development-only Rails adapter for conventional Rails 7.1 through 8.1 ERB applications on MRI/Linux.
|
|
4
|
+
The gem includes the Browser Client, so consuming applications need no Node or JavaScript package
|
|
5
|
+
manager for the taskbar. Exact release-blocking Ruby/Rails rows and Puma modes are maintained in the
|
|
6
|
+
repository's compatibility matrix. Shared wire behavior is defined only by the packaged
|
|
7
|
+
[Conformance Contract](contract/docs/index.md).
|
|
8
|
+
|
|
9
|
+
## Dependency
|
|
10
|
+
|
|
11
|
+
Add the matching product version to the development group:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
group :development do
|
|
15
|
+
gem "pi-browser-taskbar-rails", "~> 0.1.0", require: "pi/browser/taskbar/rails"
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Installer
|
|
20
|
+
|
|
21
|
+
Install into the conventional application layout and default mount:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
bin/rails generate pi_browser_taskbar:install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For a nonstandard application, select the ERB layout and mount explicitly:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
bin/rails generate pi_browser_taskbar:install \
|
|
31
|
+
--layout app/views/layouts/internal.html.erb \
|
|
32
|
+
--mount /internal/pi
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The generator plans every edit before writing and refuses ambiguous layouts, unsupported templates,
|
|
36
|
+
route conflicts, unclear head markup, and edited generated sections.
|
|
37
|
+
|
|
38
|
+
## Generated integration
|
|
39
|
+
|
|
40
|
+
The generator owns checksummed sections in a development initializer, `config/routes.rb`, and each
|
|
41
|
+
selected ERB layout. The initializer activates the isolated engine and Rails filename annotations;
|
|
42
|
+
the layout helper emits package-served assets and bounded bootstrap data. Rails processes connect to
|
|
43
|
+
the package broker rather than owning Pi themselves.
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
Use Rails-native syntax in the generated development initializer:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
Pi::Browser::Taskbar::Rails.configure do |config|
|
|
51
|
+
config.mount_path = "/dev/pi-browser-taskbar"
|
|
52
|
+
config.enabled = true
|
|
53
|
+
config.allowed_hosts = []
|
|
54
|
+
config.executable = "pi"
|
|
55
|
+
config.project_root = Rails.root.to_s
|
|
56
|
+
config.task_timeout = 1_800
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The matching `PI_BROWSER_TASKBAR_*` environment fallbacks and the normative field semantics are in
|
|
61
|
+
[Server-owned configuration and activation](contract/docs/index.md#server-owned-configuration-and-activation).
|
|
62
|
+
Restart Rails after a configuration change.
|
|
63
|
+
|
|
64
|
+
## Verification
|
|
65
|
+
|
|
66
|
+
Start Rails in development, open a host page, and confirm the lower-left **Page task** launcher is
|
|
67
|
+
present. Run a whole-page task, mark an element for a focused task, stop a running task, start a new
|
|
68
|
+
session, and follow a Turbo navigation. Repository contributors run `bin/verify` at the monorepo root; `examples/rails/` names the same
|
|
69
|
+
scenarios and stable selectors.
|
|
70
|
+
|
|
71
|
+
## Diagnosis
|
|
72
|
+
|
|
73
|
+
If the launcher is absent, confirm Rails is in development, the generated route and layout markers
|
|
74
|
+
remain intact, and the initializer has not disabled the adapter. If it is unavailable, run `pi
|
|
75
|
+
--mode rpc` from the configured project root and inspect only the adapter's safe diagnostics. For
|
|
76
|
+
host, CSRF, source-hint, busy, and cancellation symptoms, use the packaged [troubleshooting guide](docs/troubleshooting.md).
|
|
77
|
+
|
|
78
|
+
## Updates
|
|
79
|
+
|
|
80
|
+
Update the gem within the matching product version, then rerun the generator. It reports a current
|
|
81
|
+
installation or updates only recognized checksummed content. Changing an installed mount requires
|
|
82
|
+
uninstalling first.
|
|
83
|
+
|
|
84
|
+
## Security
|
|
85
|
+
|
|
86
|
+
Rails supplies native session CSRF, normalized host/peer information, filtered request parameters,
|
|
87
|
+
and development activation. The shared threat model and remote-access rules are in the packaged
|
|
88
|
+
[security guide](docs/security.md); normative invariants remain in the
|
|
89
|
+
[Conformance Contract](contract/docs/index.md#remote-development-access-and-diagnostics).
|
|
90
|
+
|
|
91
|
+
## Uninstall
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
bin/rails destroy pi_browser_taskbar:install
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Uninstall preflights all owned sections and removes all recognized content or nothing. It reports the
|
|
98
|
+
development dependency for manual removal and does not delete credentials, sessions, broker runtime
|
|
99
|
+
artifacts, or unrelated host code.
|
|
100
|
+
|
|
101
|
+
## Changelog and example
|
|
102
|
+
|
|
103
|
+
See the [Rails changelog](CHANGELOG.md) and repository path `examples/rails/` for the executable Rails
|
|
104
|
+
ERB/Turbo example.
|
|
105
|
+
|
|
106
|
+
## Matching-version contract
|
|
107
|
+
|
|
108
|
+
This adapter version is `0.1.0`. Use the same product version shown by the Phoenix adapter and the
|
|
109
|
+
root `VERSION`; the packaged [Conformance Contract](contract/docs/index.md) is the offline normative
|
|
110
|
+
reference for both adapters.
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# Conformance Contract v1
|
|
2
|
+
|
|
3
|
+
## Authority and scope
|
|
4
|
+
|
|
5
|
+
This directory is the normative shared source for cross-adapter wire behavior. Schemas and fixtures
|
|
6
|
+
are test-time authority; they are not a Ruby or Elixir runtime dependency and do not generate
|
|
7
|
+
adapter implementation code.
|
|
8
|
+
|
|
9
|
+
The executable foundation establishes fixture formats and the first task/context shapes. Both
|
|
10
|
+
whole-page adapter tracer bullets execute the same black-box HTTP scenario from built artifacts in
|
|
11
|
+
clean conventional hosts against a deterministic fake Pi peer. Later tracer bullets extend these
|
|
12
|
+
scenarios without moving authority into either adapter.
|
|
13
|
+
|
|
14
|
+
## Versioning
|
|
15
|
+
|
|
16
|
+
Every contract artifact declares integer `contract_version: 1`. Product package versions are
|
|
17
|
+
independently lockstepped by the root `VERSION` file. A contract version changes only when a client
|
|
18
|
+
must branch on incompatible wire behavior.
|
|
19
|
+
|
|
20
|
+
## Server-owned configuration and activation
|
|
21
|
+
|
|
22
|
+
Native framework development mode is an immutable activation boundary. Outside Rails development or
|
|
23
|
+
a Phoenix `Mix.env() == :dev` compilation, adapters mount no routes, emit no taskbar assets, and own
|
|
24
|
+
no Pi process even when `enabled` is configured true. In development, `enabled` defaults to true; an
|
|
25
|
+
explicit false compiles or boots routes, assets, and Pi ownership out together. Changes take effect
|
|
26
|
+
only after the framework's required restart or recompile.
|
|
27
|
+
|
|
28
|
+
The complete shared semantic configuration surface is `enabled`, `allowed_hosts`, `executable`,
|
|
29
|
+
`project_root`, and `task_timeout`. Framework-native configuration has precedence over matching
|
|
30
|
+
`PI_BROWSER_TASKBAR_ENABLED`, `PI_BROWSER_TASKBAR_ALLOWED_HOSTS`,
|
|
31
|
+
`PI_BROWSER_TASKBAR_EXECUTABLE`, `PI_BROWSER_TASKBAR_PROJECT_ROOT`, and
|
|
32
|
+
`PI_BROWSER_TASKBAR_TASK_TIMEOUT` environment fallbacks, which have precedence over defaults. The
|
|
33
|
+
defaults are enabled in development, an empty remote-host allowlist, `pi` resolved from `PATH`, the
|
|
34
|
+
canonical host project root, and 1,800 seconds. Timeout values are integer seconds from 60 through
|
|
35
|
+
86,400. Environment host lists are comma-separated; native host lists are framework lists.
|
|
36
|
+
|
|
37
|
+
When enabled, malformed booleans, host entries, executable values, project roots, and timeouts fail
|
|
38
|
+
startup with the affected setting name. A disabled adapter need not validate its inactive settings.
|
|
39
|
+
Configuration is resolved, canonicalized, and fixed at startup; it is not exposed as a browser API.
|
|
40
|
+
Browser requests cannot select or override the executable, `--mode rpc` arguments, inherited server
|
|
41
|
+
environment, working directory, timeout, protocol bounds, route behavior, or security behavior.
|
|
42
|
+
|
|
43
|
+
Adapters resolve the configured project root to an existing canonical directory and spawn the
|
|
44
|
+
configured executable directly, without a shell, as `executable --mode rpc` in that directory. Pi
|
|
45
|
+
inherits the development server environment unchanged. A missing or non-executable command produces
|
|
46
|
+
only the sanitized unavailable session state; the optional adapter failure does not prevent either
|
|
47
|
+
host application from booting.
|
|
48
|
+
|
|
49
|
+
## Remote development access and diagnostics
|
|
50
|
+
|
|
51
|
+
Every route and asset request must pass both the framework-normalized request host and client peer
|
|
52
|
+
checks. A loopback peer may use `localhost`, a syntactically valid subdomain of `.localhost`,
|
|
53
|
+
`127.0.0.1`, `::1`, or an explicitly configured exact allowed host. A non-loopback peer is denied
|
|
54
|
+
unless the normalized host exactly matches an entry in a non-empty `allowed_hosts` list. Host
|
|
55
|
+
normalization lowercases DNS names, removes their optional final dot, and canonicalizes IP literal
|
|
56
|
+
spelling before exact comparison.
|
|
57
|
+
|
|
58
|
+
Allowed-host entries are bare exact DNS names or IPv4/IPv6 literals only. Schemes, ports, paths,
|
|
59
|
+
wildcards, suffix patterns, scoped IP literals, empty list entries, and empty comma-separated entries
|
|
60
|
+
are startup errors naming `allowed_hosts`. The empty list is the safe default and enables no remote
|
|
61
|
+
access. There is no CIDR, wildcard, remote-access boolean, or suffix matching.
|
|
62
|
+
|
|
63
|
+
Rails uses only `request.host` and `request.remote_ip`; Phoenix uses only `conn.host` and
|
|
64
|
+
`conn.remote_ip`. Those values already reflect the host application's framework and trusted-proxy
|
|
65
|
+
configuration. Neither adapter reads `Forwarded`, `X-Forwarded-Host`, `X-Forwarded-For`, or similar
|
|
66
|
+
headers itself. Proxy deployments must configure the host framework's trusted proxies rather than
|
|
67
|
+
expecting a second taskbar-specific forwarding model.
|
|
68
|
+
|
|
69
|
+
Mutations remain protected by each framework's native session-bound CSRF check and return the stable
|
|
70
|
+
`invalid_csrf` code with a safe message when rejected. Adapter responses add no permissive CORS
|
|
71
|
+
headers. Browser validation failures return `invalid_task` and a fixed safe message rather than
|
|
72
|
+
copying attacker-controlled fields or local details.
|
|
73
|
+
|
|
74
|
+
A non-empty allowlist causes the server bootstrap to expose only a boolean remote-access warning
|
|
75
|
+
state, never the host list. When that state is active on an HTTP page, the taskbar persistently warns
|
|
76
|
+
that remote HTTP is unencrypted and is suitable only on a trusted network. HTTPS does not show the
|
|
77
|
+
unencrypted-transport warning. This development mode provides neither transport encryption nor host
|
|
78
|
+
user authentication.
|
|
79
|
+
|
|
80
|
+
Adapters do not log browser context, prompts, commands, inherited environment, absolute paths, raw
|
|
81
|
+
Pi/provider errors, stderr, or protocol records. Rails additionally registers host parameter filters
|
|
82
|
+
for `prompt` and `context`; Phoenix's forwarded Plug reads the body without controller parameter
|
|
83
|
+
logging. Browser-visible task/session diagnostics and adapter-generated errors use bounded fixed
|
|
84
|
+
messages and stable codes only.
|
|
85
|
+
|
|
86
|
+
## Shared HTTP API and stable errors
|
|
87
|
+
|
|
88
|
+
Both adapters expose the same JSON API below their generated, application-owned mount base:
|
|
89
|
+
|
|
90
|
+
- `GET /state` returns the complete current snapshot;
|
|
91
|
+
- `POST /tasks` admits one normalized task or rejects it atomically;
|
|
92
|
+
- `DELETE /tasks/:id` stops the retained task under the cancellation rules below;
|
|
93
|
+
- `POST /session/reset` performs the confirmed session switch described below.
|
|
94
|
+
|
|
95
|
+
Successful reads and mutations return the complete canonical snapshot and `Cache-Control: no-store`.
|
|
96
|
+
Mutation requests are never retried; an ambiguous result is reconciled through `GET /state`. Snapshot
|
|
97
|
+
session states are `starting`, `ready`, `busy`, `resetting`, and `unavailable`; retained task states
|
|
98
|
+
are `running`, `cancelling`, `completed`, `failed`, and `cancelled`. Clients branch only on contract
|
|
99
|
+
version, these enums, and the stable codes below, never presentation messages.
|
|
100
|
+
|
|
101
|
+
| Stable code | HTTP class | Meaning |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| `forbidden` | 403 | Host or client access failed closed. |
|
|
104
|
+
| `invalid_csrf` | 422 | The framework-native session CSRF check rejected a mutation. |
|
|
105
|
+
| `invalid_task` | 422 | JSON, request shape, normalization, or bounds are invalid. |
|
|
106
|
+
| `busy` | 409 | Another task is running or cancelling. |
|
|
107
|
+
| `task_not_found` | 404 | The requested task is not retained. |
|
|
108
|
+
| `task_not_cancellable` | 409 | The retained task is terminal and cannot be stopped. |
|
|
109
|
+
| `reset_while_busy` | 409 | The session cannot reset in its current state. |
|
|
110
|
+
| `session_reset_rejected` | 409 | Pi rejected the in-process session switch. |
|
|
111
|
+
| `unavailable` | 503 | No verified Pi owner is ready for the operation. |
|
|
112
|
+
|
|
113
|
+
Error messages are fixed safe presentation text. When canonical state exists, conflict, not-found,
|
|
114
|
+
and unavailable responses include it as `snapshot`; callers use the code rather than matching the
|
|
115
|
+
message.
|
|
116
|
+
|
|
117
|
+
## Initial task request
|
|
118
|
+
|
|
119
|
+
A task request has exactly two fields:
|
|
120
|
+
|
|
121
|
+
- `prompt`: a non-empty normalized dedicated instruction bounded to 4,000 UTF-8 bytes;
|
|
122
|
+
- `context`: required normalized browser reference data conforming to
|
|
123
|
+
`browser-context.v1.schema.json`.
|
|
124
|
+
|
|
125
|
+
Unknown fields are invalid at every modeled level. The browser representation is reference data,
|
|
126
|
+
not instruction text. Native adapters will independently validate and normalize requests before
|
|
127
|
+
constructing prompts.
|
|
128
|
+
|
|
129
|
+
## Normalized browser context
|
|
130
|
+
|
|
131
|
+
A browser context declares its contract version, sanitized location, optional confident route
|
|
132
|
+
metadata, structural page snapshot, zero to eight ordered, selector-unique advisory focus points,
|
|
133
|
+
and explicit truncation records. A zero-length focus list means a whole-page task. Every focus point
|
|
134
|
+
retains its stable selector, conservative source hint, up to eight outer-to-inner ancestor summaries,
|
|
135
|
+
and a bounded subtree; focus never removes the whole-page snapshot.
|
|
136
|
+
|
|
137
|
+
Location retains only an HTTP(S) origin, path, and unique query names in encounter order. URL
|
|
138
|
+
credentials, fragments, and query values are forbidden. Route metadata is either `null` or the
|
|
139
|
+
bounded method, pattern, handler, and nullable action supplied by a confident adapter seam.
|
|
140
|
+
|
|
141
|
+
Snapshot nodes retain only tag, role, accessible name, normalized direct visible text, identifier,
|
|
142
|
+
bounded class tokens, `name`/`type`/`placeholder`/`data-testid`, semantic control state, sanitized
|
|
143
|
+
HTTP(S) `href`/`src` references, confident advisory source hints, and children. Browser capture
|
|
144
|
+
excludes taskbar content, metadata, scripts, styles, templates, non-rendered or inert content,
|
|
145
|
+
hidden inputs, form values, editable
|
|
146
|
+
content, arbitrary attributes, iframe contents, and nested Shadow DOM. It never serializes HTML.
|
|
147
|
+
|
|
148
|
+
A source hint has an `available`, `ambiguous`, `external`, or `unavailable` status. Available hints
|
|
149
|
+
contain one or two `template`, `definition`, or `caller` references; every other status has no
|
|
150
|
+
references. References carry a project-relative forward-slash path, `line` or `template` precision,
|
|
151
|
+
and optional positive line and bounded symbol. Absolute, traversing, malformed, dependency-owned,
|
|
152
|
+
or otherwise external paths are never retained.
|
|
153
|
+
|
|
154
|
+
Rails source hints come only from native rendered-template ERB filename annotations. The innermost
|
|
155
|
+
unique well-formed range enclosing a node may provide one project-relative `template` reference
|
|
156
|
+
with `template` precision and no line or element-origin claim. An invalid, overlapping, external,
|
|
157
|
+
missing, or browser-displaced inner boundary is classified rather than replaced with a surrounding
|
|
158
|
+
layout hint. Cached and helper-generated markup may retain its enclosing template-level hint.
|
|
159
|
+
|
|
160
|
+
Normalized lengths are measured in UTF-8 bytes: request 128 KiB, context 96 KiB, prompt 4,000,
|
|
161
|
+
page snapshot 48 KiB/750 nodes/depth 12, and combined focus detail 48 KiB. Focus subtrees are
|
|
162
|
+
limited to 100 nodes/depth 6. Focus selectors and complete source hints are reserved before detail;
|
|
163
|
+
the remaining focus allocation is shared evenly in mark order, then the page receives the remaining
|
|
164
|
+
context allocation up to its own bound. Strings use the bounds encoded by `x-maxUtf8Bytes` in the
|
|
165
|
+
schema. Truncation occurs only at Unicode code-point boundaries, retains page and focused subtree
|
|
166
|
+
nodes breadth-first, and reports affected page or `focus:1` through `focus:8` sections with canonical
|
|
167
|
+
`bytes`, `nodes`, `depth`, and `string` reasons.
|
|
168
|
+
|
|
169
|
+
Both adapters independently normalize NFC Unicode, line endings, controls, structural whitespace,
|
|
170
|
+
tag/method case, optional empty fields, and truncation order before validation. Unknown fields,
|
|
171
|
+
duplicate query names or focus selectors, malformed focus structures, unsafe locations, and values
|
|
172
|
+
outside any allocation are invalid.
|
|
173
|
+
|
|
174
|
+
## Prompt envelope
|
|
175
|
+
|
|
176
|
+
Only the normalized `prompt` field is an instruction. Adapters append canonical context JSON in this
|
|
177
|
+
exact separately delimited envelope:
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
<normalized prompt>
|
|
181
|
+
|
|
182
|
+
--- BEGIN UNTRUSTED BROWSER CONTEXT ---
|
|
183
|
+
<canonical JSON context with HTML-significant characters escaped>
|
|
184
|
+
--- END UNTRUSTED BROWSER CONTEXT ---
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The delimiter, JSON serialization, and escaping are covered by prompt goldens. Text inside browser
|
|
188
|
+
context is untrusted reference data even when it resembles instructions. Visible text and URL paths
|
|
189
|
+
may reach the configured Pi/model provider; no adapter claims automatic secret or PII detection.
|
|
190
|
+
|
|
191
|
+
## Pi progress, output, and safe failures
|
|
192
|
+
|
|
193
|
+
Startup remains `starting` until a correlated successful `get_state` response supplies both a
|
|
194
|
+
non-empty session identity and model. A successful correlated `prompt` response means accepted and
|
|
195
|
+
does not finish the task. The adapters apply this shared event mapping independently:
|
|
196
|
+
|
|
197
|
+
| Pi event | Canonical effect |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| `agent_start` | Activity `Pi is working` |
|
|
200
|
+
| `agent_end` | Activity `Pi finished a turn`; the task remains running |
|
|
201
|
+
| `message_update.text_delta` | Append output, retaining only the newest valid UTF-8 32 KiB suffix |
|
|
202
|
+
| `tool_execution_start` / `tool_execution_update` | Activity `Running <bounded tool name>` |
|
|
203
|
+
| `tool_execution_end` | Activity `Finished <bounded tool name>` or `Tool failed <bounded tool name>` |
|
|
204
|
+
| `compaction_start` | Activity `Compacting conversation` |
|
|
205
|
+
| successful `compaction_end` | Activity `Conversation compacted` or `Retrying after compaction` |
|
|
206
|
+
| `auto_retry_start` | Activity `Retrying request (<attempt>/<maximum>)` when counts are valid |
|
|
207
|
+
| successful `auto_retry_end` | Activity `Pi is working` |
|
|
208
|
+
| `agent_settled` | The sole normal `completed` boundary |
|
|
209
|
+
|
|
210
|
+
Once older output is removed, `output_truncated` remains true and the Browser Client says that it is
|
|
211
|
+
showing the newest 32 KiB. Truncation never splits a Unicode code point. Dialog extension requests
|
|
212
|
+
(`select`, `confirm`, `input`, and `editor`) receive a correlated `extension_ui_response` with
|
|
213
|
+
`cancelled: true`; fire-and-forget and unknown future events are ignored without exposing their raw
|
|
214
|
+
records.
|
|
215
|
+
|
|
216
|
+
Rejected prompt/abort commands, message errors, exhausted retries, failed compaction, malformed,
|
|
217
|
+
non-object, unterminated, or oversized JSONL records, unexpected correlated responses, timeouts, and
|
|
218
|
+
process exits produce fixed task/session diagnostics without copying provider errors, protocol
|
|
219
|
+
records, command lines, environment values, or paths into browser state or adapter logs. Protocol
|
|
220
|
+
loss during an active task fails the retained task and replaces the Pi process before new work is
|
|
221
|
+
accepted. A message, retry, or compaction terminal error waits for `agent_settled` before releasing
|
|
222
|
+
the busy session. Unknown future event types remain forward-compatible and do not change state.
|
|
223
|
+
|
|
224
|
+
The progress and failure RPC transcripts, native runtime tests, packaged clean-host flows, and root
|
|
225
|
+
semantic comparison exercise the same mapping in Rails and Phoenix while normalizing only opaque
|
|
226
|
+
identities and timestamps.
|
|
227
|
+
|
|
228
|
+
## Task cancellation
|
|
229
|
+
|
|
230
|
+
`DELETE /tasks/:id` sends one correlated Pi `abort` command for a retained `running` task and returns
|
|
231
|
+
the complete canonical `cancelling` snapshot with HTTP 202. The session remains `busy`, the task has
|
|
232
|
+
no `finished_at`, and completion waits for Pi's `agent_settled` event. At that boundary the task
|
|
233
|
+
becomes `cancelled`, receives `finished_at` and `Task stopped` activity, and the session returns to
|
|
234
|
+
`ready`.
|
|
235
|
+
|
|
236
|
+
Cancellation is idempotent: repeating the request while `cancelling` returns the same 202 lifecycle
|
|
237
|
+
without sending another abort, and repeating it after `cancelled` returns the retained snapshot with
|
|
238
|
+
HTTP 200. A different or forgotten ID returns `404 task_not_found`; a retained `completed` or
|
|
239
|
+
`failed` task returns `409 task_not_cancellable`. Error responses include the current snapshot and
|
|
240
|
+
all cancellation mutations remain protected by each framework's native CSRF and access checks.
|
|
241
|
+
Stopping is not transactional and cannot roll back file changes Pi already made.
|
|
242
|
+
|
|
243
|
+
## Timeout and process recovery
|
|
244
|
+
|
|
245
|
+
A configured task timeout fails the active task with retained bounded output and safe diagnostics,
|
|
246
|
+
then replaces Pi before accepting more work. Cancellation has its own bounded deadline: if Pi does
|
|
247
|
+
not reach `agent_settled`, the task becomes `cancelled` with safe diagnostics and the adapter replaces
|
|
248
|
+
the process rather than claiming the old conversation survived.
|
|
249
|
+
|
|
250
|
+
Replacement sends TERM to the owned Pi process group, waits a bounded interval, escalates to KILL,
|
|
251
|
+
and reaps the child before starting a replacement. The public session identity and model are cleared
|
|
252
|
+
while replacement is `starting`; a successful startup exposes a new opaque identity. Unexpected exit
|
|
253
|
+
while busy preserves terminal task evidence, while idle exit retains no invented task. Startup and
|
|
254
|
+
replacement attempts are bounded, ending in `unavailable` when Pi is missing, non-executable, or
|
|
255
|
+
repeatedly fails to establish a valid startup state. These failures do not terminate the host
|
|
256
|
+
application.
|
|
257
|
+
|
|
258
|
+
Equivalent Rails and Phoenix fake-Pi runtime scenarios cover task timeout, missed abort settlement,
|
|
259
|
+
startup failure, active and idle crashes, successful replacement, exhausted replacement, and owned
|
|
260
|
+
child cleanup.
|
|
261
|
+
|
|
262
|
+
## Rails broker topology
|
|
263
|
+
|
|
264
|
+
Rails serving processes never own Pi. One gem-packaged external broker owns Pi and canonical task and
|
|
265
|
+
session state for the canonical checkout path and OS user. A user-private runtime directory, exclusive
|
|
266
|
+
OS lock, Unix socket, atomic endpoint metadata, protocol version, canonical identity, and fresh
|
|
267
|
+
instance token make concurrent single-process, threaded, preloaded, clustered, phased, and separate
|
|
268
|
+
server invocations converge on the same verified broker. An incompatible or unverifiable live broker
|
|
269
|
+
is not terminated, and failure to establish a verified connection is unavailable rather than a
|
|
270
|
+
process-local fallback.
|
|
271
|
+
|
|
272
|
+
Each Rails process lazily retains one PID-aware client outside application reload paths. Reloads keep
|
|
273
|
+
the connection; a fork discards only the child's inherited socket and mutex state before lazy
|
|
274
|
+
reconnection. The broker remains alive while a client is connected or work is active. Its five-minute
|
|
275
|
+
grace begins only after both conditions become false, so disconnected work settles before the timer
|
|
276
|
+
starts. Graceful broker shutdown closes and reaps the Pi process group with bounded TERM-to-KILL
|
|
277
|
+
cleanup; a broker or Pi replacement truthfully starts a new conversation.
|
|
278
|
+
|
|
279
|
+
## Browser reconciliation and host navigation
|
|
280
|
+
|
|
281
|
+
Each mounted Browser Client reads the complete canonical snapshot independently. It polls every 500
|
|
282
|
+
milliseconds while the session is `starting`, `resetting`, or `busy`, or the task is `running` or
|
|
283
|
+
`cancelling`, and every 30 seconds while stable. Failed reads preserve the last rendered snapshot
|
|
284
|
+
and retry with exponential delays bounded between one and 30 seconds. One isolated network failure
|
|
285
|
+
stays quiet; repeated network failures or other read errors show a retry indication. Returning
|
|
286
|
+
browser visibility triggers an immediate read.
|
|
287
|
+
|
|
288
|
+
A task, cancellation, or reset mutation is sent exactly once. Any HTTP failure or ambiguous network
|
|
289
|
+
result is reconciled with `GET /state`; the client never retries the mutation. This makes admission,
|
|
290
|
+
progress, output, cancellation, reset, terminal feedback, and busy controls converge across tabs
|
|
291
|
+
without browser-to-browser coordination.
|
|
292
|
+
|
|
293
|
+
The client appends one Shadow DOM host as a direct body child, marks it permanent for partial
|
|
294
|
+
navigation, and reuses an existing host if packaged scripts execute again. Before a body replacement
|
|
295
|
+
it moves that host into the incoming body; navigation completion refreshes canonical state. Full
|
|
296
|
+
controller/document navigation mounts one new host and reads state. Live page patches leave the host
|
|
297
|
+
outside their owned roots. Removed or selector-displaced marked elements are discarded with their
|
|
298
|
+
outlines; source hints are captured again from the current DOM at submission rather than retained
|
|
299
|
+
across patches. Draft text remains browser-local while a surviving partial-navigation host is reused.
|
|
300
|
+
|
|
301
|
+
Current-browser acceptance exercises two tabs, ambiguous submission, shared busy,
|
|
302
|
+
progress, cancellation, and output, plus idle/active partial navigation, live patching, and full
|
|
303
|
+
navigation remounts.
|
|
304
|
+
|
|
305
|
+
## Corner composer and taskbar accessibility
|
|
306
|
+
|
|
307
|
+
The Browser Client owns one lower-left launcher and compact composer entirely inside its Shadow DOM.
|
|
308
|
+
It uses only native controls, inline taskbar markup, system fonts, and taskbar styles. The open order is
|
|
309
|
+
Pi identity/model, task focus and optional removable marks, task instruction, lifecycle feedback, then
|
|
310
|
+
footer status and the task action. Zero marks says **Whole page**; one through eight marks remain
|
|
311
|
+
advisory focus points with whole-page surroundings.
|
|
312
|
+
|
|
313
|
+
The stable visible states are **Connecting**, **Ready**, **Working**, **Finished**, **Stopped**, and
|
|
314
|
+
**Unavailable**. They are exposed as text and programmatic state, not color alone. One atomic polite
|
|
315
|
+
live region announces changed activity and terminal results; visible errors use that same announcer so
|
|
316
|
+
status, activity, and error elements do not produce duplicate live regions. Bounded output remains
|
|
317
|
+
keyboard-scrollable and labelled, and a stopped task retains the warning that existing file changes
|
|
318
|
+
were not rolled back.
|
|
319
|
+
|
|
320
|
+
Opening moves focus to the labelled instruction field; collapse or `Escape` returns focus to the
|
|
321
|
+
launcher. **Mark element** exposes pressed state and visible pointer/focus guidance: a keyboard user
|
|
322
|
+
may focus a host-page element and press Enter or Space, while `Escape` cancels and returns focus to
|
|
323
|
+
**Mark element**. Removing or clearing marks returns focus to a surviving remove control or **Mark
|
|
324
|
+
element**. Native buttons cover submission and stopping. Active work disables editing, marking, and
|
|
325
|
+
clearing while leaving **Stop task** available.
|
|
326
|
+
|
|
327
|
+
The composer fits the available narrow viewport, caps its block size so content remains scrollable at
|
|
328
|
+
200% zoom, and removes animation and transition effects under `prefers-reduced-motion: reduce`.
|
|
329
|
+
Automated acceptance extracts each Browser Client from the built gem or Hex archive and runs both
|
|
330
|
+
equivalent example surfaces in current Playwright Chromium, Firefox, and WebKit. It covers every
|
|
331
|
+
material lifecycle state, whole-page and focused tasks, mark removal/clear, progress/output, stop,
|
|
332
|
+
unavailable/network recovery, cross-tab reconciliation, Turbo navigation,
|
|
333
|
+
LiveView navigation/patching, taskbar-owned axe results, names/states/live regions, keyboard focus,
|
|
334
|
+
Shadow DOM isolation, desktop/narrow reflow, 200% CSS-zoom reflow emulation, and reduced motion. Artifact hashes, exact engine
|
|
335
|
+
versions, scope, and scenarios form deterministic build evidence.
|
|
336
|
+
|
|
337
|
+
The WCAG 2.2 AA target and automated checks apply only to the Shadow-DOM taskbar interface; they
|
|
338
|
+
neither test nor claim accessibility for the host application.
|
|
339
|
+
|
|
340
|
+
## Session reset
|
|
341
|
+
|
|
342
|
+
`POST /session/reset` is accepted only while the session is `ready`; a running, cancelling, or
|
|
343
|
+
already-resetting session returns `409 reset_while_busy` with the unchanged snapshot. An accepted
|
|
344
|
+
request enters `resetting`, sends Pi's supported correlated `new_session` command, then sends
|
|
345
|
+
`get_state` and returns HTTP 202 only after the replacement is confirmed `ready`. The public session
|
|
346
|
+
identity changes and retained task feedback is cleared.
|
|
347
|
+
|
|
348
|
+
Pi reports an extension veto as a successful `new_session` response with `data.cancelled: true`.
|
|
349
|
+
Adapters return `409 session_reset_rejected` and preserve the old session identity and retained task
|
|
350
|
+
snapshot exactly. A failed RPC command, invalid confirmation, protocol failure, or process exit during
|
|
351
|
+
the switch invokes process replacement as recovery; an ordinary accepted or rejected switch does not
|
|
352
|
+
replace the healthy process.
|
|
353
|
+
|
|
354
|
+
## Fixture manifest
|
|
355
|
+
|
|
356
|
+
`fixtures/manifest.json` is the only fixture registry. Each entry identifies a schema, a repository-
|
|
357
|
+
relative JSON file, and whether validation must succeed. An entry expecting rejection must include
|
|
358
|
+
an error fragment and is considered passing only when the validator rejects it for that reason.
|
|
359
|
+
This prevents an invalid fixture from becoming inert sample data.
|
|
360
|
+
|
|
361
|
+
Negative fixtures prove unknown/malformed fields, duplicate query names, URL credentials, path
|
|
362
|
+
query/fragment leakage, UTF-8 byte bounds, aggregate node bounds, and the two-source-reference limit
|
|
363
|
+
are rejected. Shared source fixtures exercise Phoenix template and definition/caller precision plus
|
|
364
|
+
all non-available classifications. Rich whole-page and prompt fixtures exercise every semantic node
|
|
365
|
+
section and the trusted-instruction/untrusted-context boundary.
|
|
366
|
+
|
|
367
|
+
## Other executable formats
|
|
368
|
+
|
|
369
|
+
HTTP scenarios, prompt goldens, and Pi RPC transcript formats are versioned alongside browser
|
|
370
|
+
context. An HTTP scenario may name a contract task fixture as its request body. The deterministic
|
|
371
|
+
fake RPC peer replays transcript `receive`/`send` steps. Shared cancellation scenarios cover accepted
|
|
372
|
+
and repeated aborts, wrong and completed task IDs, and the settled terminal snapshot. Shared reset
|
|
373
|
+
scenarios and transcripts cover accepted, busy, and extension-rejected switches plus mandatory state
|
|
374
|
+
confirmation. Both packages execute these flows; root conformance rejects semantic drift except
|
|
375
|
+
opaque IDs and timestamps.
|
|
376
|
+
Only `prompt` is instructional; context is canonical JSON inside an explicitly untrusted delimiter,
|
|
377
|
+
with HTML-significant characters escaped. Visible text and URL paths may reach the developer's
|
|
378
|
+
configured Pi/model provider, so the taskbar is unsuitable for sensitive datasets.
|
|
379
|
+
|
|
380
|
+
See the [traceability index](../traceability.md) for normative parent sections and their eventual
|
|
381
|
+
acceptance seams.
|