F1-AI-Selection-Editor
Global, app-wide feature. The AI Text Selection Editor is a cross-app overlay that activates wherever a user can select or edit text inside the Laravel Control Center. It is not scoped to one page, one editor, or the last AI output. Treat it like Notion's AI selection bar: select → ask → preview → apply, everywhere.
Correction lock. Any earlier wording that implied this feature works only on the last AI message, last text block, or a single page is superseded by this spec. If a Livewire component, Filament field, or Blade view renders user-visible text inside the Control Center, it MUST opt in to this layer (or be on the explicit deny-list). The default is on.
Canonical diagrams (Option 1). The shared diagram set (including the F1 flow) is centralized in 🧭ARCHITECTURE_DIAGRAMS (Canonical). This page may include diagrams only when they are specific to F1 and not shared elsewhere.
0. TL;DR
One global JavaScript layer + one Laravel API endpoint + one Eloquent audit table give every text surface in the Control Center a Notion-AI-style selection toolbar: Rewrite · Improve · Translate · Simplify · Expand · Summarize · Transform · Custom prompt. The same endpoint is reused by the iPhone client for parity.
1. Scope — where it MUST work
Enable globally in every surface where the user reads or writes text:
project descriptions
task descriptions
task comments
notes
agent prompts (run composer)
agent run summaries
AI-generated responses (assistant messages)
AI output review areas (plan/approve)
technical specifications
documentation editor (Markdown)
markdown editor (generic)
changelog editor
project map docs
database schema docs
architecture docs
API docs
server params descriptions
Git workflow docs
RAG docs
Claude Code docs
OpenAI docs
LiteSpeed / CentOS deployment docs
web app plan
iPhone app plan
settings descriptions
admin notes
review screens
approval screensIf a future surface adds editable text, the layer is on by default. Explicit opt-out requires the attribute data-ai-editor="off".
2. Out of scope (explicit deny-list)
- Password, token, secret, API-key inputs (
data-ai-editor="off"is forced on these by the base form components).
- File paths inside the file tree.
- Diff hunks (the diff viewer has its own "Explain this diff" affordance, not the selection bar).
- Code inside Monaco read-only views (selection works, but the toolbar offers only Explain and Translate to plain language there).
3. UX flow
1. User selects text (mouse drag, double-click, Shift+Arrow, Cmd-A, etc.) anywhere
on a surface that has not opted out.
2. A floating toolbar appears anchored to the selection rectangle (Notion-style).
It shows a primary "Ask AI" button + quick actions.
3. User picks an action (or types a custom prompt in the inline input).
4. The Control Center shows a streaming preview panel (or popover) with the result.
5. User chooses:
- Replace selection
- Insert below selection
- Copy to clipboard
- Discard
Replace/Insert are only enabled on editable surfaces; read-only surfaces
show Copy + Discard.
6. The transformation is logged in ai_text_transformations.Keyboard: Cmd+K / Ctrl+K opens the bar on the current selection. Esc dismisses. Cmd+Enter accepts the suggestion.
4. Action catalog (v1)
| Action | Key | Default model | Notes |
|---|---|---|---|
| Rewrite | rewrite | gpt-4.1 | Same meaning, different wording. |
| Improve | improve | gpt-4.1 | Grammar + clarity + tone consistency. |
| Translate | translate | gpt-4.1 | Target language picker; remembers last 3. |
| Simplify | simplify | gpt-4.1 | Plain language, shorter sentences. |
| Expand | expand | gpt-4.1 | Add detail without changing facts. |
| Summarize | summarize | gpt-4.1 | 3-bullet default; toggle to 1-line / paragraph. |
| Transform | transform | gpt-4.1 | Free-form natural-language instruction. |
| Continue writing | continue | gpt-4.1 | Editable surfaces only; uses cursor instead of selection. |
| Fix spelling/grammar | fix | gpt-4.1 | Minimal-diff edit. |
| Make shorter | shorten | gpt-4.1 | at most 60% of original length. |
| Make longer | lengthen | gpt-4.1 | at least 140% of original length. |
| Change tone | tone | gpt-4.1 | Sub-picker: professional / friendly / concise / persuasive / neutral. |
| Explain | explain | gpt-4.1 | Returns explanation, never replaces. |
| Convert to checklist | to_checklist | gpt-4.1 | Markdown task list. |
| Convert to table | to_table | gpt-4.1 | Best-effort Markdown table. |
| Custom | custom | per-user setting | Free-text prompt; full instruction visible. |
Provider selection follows the global Agent Provider preference (openai / claude_api). Claude Code is not a provider for this feature.
5. Source-context detection
When the layer fires, it gathers a structured context envelope so the AI knows where the text came from. The envelope is part of the API payload:
{
"selection": "...",
"surface": {
"kind": "agent_run_summary",
"entity_type": "agent_run",
"entity_id": 4128,
"field": "summary",
"locale": "en",
"editable": true,
"format": "markdown"
},
"context": {
"surrounding_before": "...up to 2000 chars before selection...",
"surrounding_after": "...up to 2000 chars after selection...",
"page_title": "Run #4128 — Refactor RAG indexer",
"breadcrumbs": ["Projects", "AgentWorkspace", "Runs", "#4128"],
"project_id": 17
},
"action": "improve",
"params": { "target_language": null, "tone": null, "custom_prompt": null }
}Detection rules:
surface.kindis read from the nearest ancestor element withdata-ai-surface="...". Required on every surface listed in §1.
surface.entity_type+surface.entity_idcome fromdata-ai-entity-type/data-ai-entity-id. Used for audit and (when allowed) RAG retrieval.
surface.editableisfalseon read-only surfaces (AI responses, review screens, doc renderers).
surface.formatismarkdown(default),plain, orcode. Affects the system prompt.
surrounding_before/surrounding_aftercome from the host editor; for plain inputs we send up to +/-2000 chars from the same field; for Markdown editors we send the same block; cross-block context is opt-in viadata-ai-context="page".
- The frontend never sends
payload_jsonof database rows. Only what is visually rendered.
6. Architecture
flowchart LR
USER([User selects text]) --> JS["ai-text-editor.js (global layer)"]
JS --> BAR["Floating toolbar + inline command bar"]
BAR -->|"POST /api/ai/text/transform"| API[Laravel API]
API --> SVC[AiTextTransformService]
SVC --> POL{Policy + rate-limit}
POL -->|allowed| PROV[AgentProviderInterface]
PROV -->|openai| OAI[OpenAI]
PROV -->|claude_api| CLA[Anthropic]
SVC --> LOG[(ai_text_transformations)]
SVC -->|SSE stream| BAR
BAR --> APPLY["Apply / Insert / Copy / Discard"]
APPLY -->|editable surfaces| HOST[Host editor sets value]Key points:
- One frontend layer. Mounted once in the global Blade layout. Not duplicated per page or per Livewire component.
- One backend service.
AiTextTransformServiceis the single funnel. Livewire components, Filament actions, and the iPhone client all hit the same endpoint.
- Streaming. Server-Sent Events stream tokens back to the toolbar. Final event includes the complete result and a
transformation_id.
- Apply is local. The backend never writes to the host record itself; the user explicitly clicks Replace/Insert, and the host editor's normal save path runs.
7. API contract
7.1 POST /api/ai/text/transform
Ability: ai:text:transform (granted by default on user tokens; revocable per token).
Rate limit: 30 / minute / user, 6 / minute / token, soft warning at 80%.
Body: the envelope in §5.
Response: text/event-stream with events:
event: started
data: {"transformation_id": 91234, "action": "improve", "model": "gpt-4.1"}
event: delta
data: {"text": "...streamed chunk..."}
event: delta
data: {"text": "...more..."}
event: done
data: {
"transformation_id": 91234,
"result": "...full text...",
"input_tokens": 412,
"output_tokens": 188,
"latency_ms": 1840,
"truncated": false
}
event: error
data: {"code": "rate_limited", "message": "..."}Non-streaming clients (iPhone fallback, tests) may send Accept: application/json and receive a single JSON body with the same done shape.
7.2 GET /api/ai/text/transformations/{id}
Returns the persisted record (selection, result, surface, action, params, model, tokens) for audit / undo / re-run.
7.3 POST /api/ai/text/transformations/{id}/rerun
Re-runs the same transformation with optional action or params overrides. Used by the toolbar's "Try again" button.
8. Persistence
New table:
Schema::create('ai_text_transformations', function (Blueprint $t) {
$t->id();
$t->foreignId('user_id')->constrained()->cascadeOnDelete();
$t->foreignId('project_id')->nullable()->constrained()->nullOnDelete();
$t->string('surface_kind', 64); // e.g. agent_run_summary
$t->string('entity_type', 64)->nullable(); // e.g. agent_run
$t->unsignedBigInteger('entity_id')->nullable();
$t->string('field', 64)->nullable();
$t->string('locale', 16)->nullable();
$t->string('format', 16)->default('markdown'); // markdown|plain|code
$t->string('action', 32); // improve, translate, ...
$t->json('params')->nullable();
$t->string('provider', 32); // openai|claude_api
$t->string('model', 64);
$t->longText('selection');
$t->longText('context_before')->nullable();
$t->longText('context_after')->nullable();
$t->longText('result')->nullable();
$t->unsignedInteger('input_tokens')->nullable();
$t->unsignedInteger('output_tokens')->nullable();
$t->unsignedInteger('latency_ms')->nullable();
$t->string('client', 16); // web|ios
$t->string('outcome', 16)->default('returned'); // returned|applied|discarded|error
$t->string('error_code', 64)->nullable();
$t->timestamps();
$t->index(['user_id', 'created_at']);
$t->index(['entity_type', 'entity_id']);
$t->index(['surface_kind']);
$t->index(['action']);
});The outcome column is updated by a follow-up PATCH /api/ai/text/transformations/{id} call from the frontend when the user clicks Apply or Discard. Retention: 90 days (configurable via agent_settings).
9. Frontend integration
9.1 Global mount
Mount once in resources/views/layouts/app.blade.php, right before </body>. The four placeholders below are Blade expressions in the real template (omitted here so the docs don't collide with Notion's mention syntax):
<div id="ai-text-editor-root"
data-ai-default-provider="[BLADE: $aiProvider]"
data-ai-default-model="[BLADE: $aiModel]"
data-ai-csrf="[BLADE: csrf_token()]"
wire:ignore></div>
<script src="[BLADE: Vite::asset('resources/js/ai-text-editor/index.ts')]" defer></script>9.2 Surface annotation
Every surface in §1 declares itself with three attributes on its outermost wrapper:
<div data-ai-surface="agent_run_summary"
data-ai-entity-type="agent_run"
data-ai-entity-id="[BLADE: $run->id]"
data-ai-editable="true"
data-ai-format="markdown">
[BLADE: Str::markdown($run->summary)]
</div>For editable Livewire/Alpine surfaces, the wrapper additionally exposes:
data-ai-apply="livewire" // adapter name
data-ai-apply-target="summary" // property/field name to write back intoThe layer reads these and calls the right apply adapter when the user clicks Replace/Insert. Apply adapters live in resources/js/ai-text-editor/adapters/.
9.3 Built-in adapters (v1)
livewire— calls a genericsetAiText(field, value)Livewire action on the nearest component.
alpine— writes tox-dataproperty by ref.
monaco— uses the Monaco editor instance API on the wrapper.
contenteditable— replaces the liveRangeof the selection.
input— setsvalueof the closest<input>/<textarea>and dispatchesinput+changeevents.
9.4 Filament + Livewire base components
Update the base form components to forward the annotation automatically:
<x-agent::textarea>→ emitsdata-ai-surface,data-ai-format,data-ai-apply="input"based on slot props.
<x-agent::markdown-editor>→data-ai-format="markdown",data-ai-apply="monaco".
<x-agent::message>(AI assistant bubble) →data-ai-editable="false",data-ai-applyabsent.
- Filament: a
HasAiTextEditorfield trait sets these attributes on every text input across every Resource.
9.5 Read-only surfaces
Read-only surfaces still get the toolbar, but with the apply menu showing only Copy and Discard. The selection is sent with surface.editable: false; the result is shown in the preview panel, not written back.
10. Backend integration
10.1 Service
final class AiTextTransformService
{
public function __construct(
private AgentProviderRegistry $providers,
private AiTextPromptBuilder $prompts,
private RateLimiter $limiter,
) {}
public function stream(User $user, TransformRequest $req): Generator;
public function persist(TransformResult $r): AiTextTransformation;
public function markOutcome(int $id, string $outcome, ?string $errorCode = null): void;
}10.2 Prompt builder
AiTextPromptBuilder returns a system + user message tuple keyed by (action, format, surface.kind). Prompts live in config/agent_workspace/ai_text_prompts.php and are versioned. Each prompt explicitly tells the model:
- Preserve meaning.
- Preserve formatting unless the action says otherwise.
- Never invent facts about entities named in the surrounding text.
- Return only the transformed text — no preamble, no explanation (except for
explain).
10.3 Provider routing
Reuses AgentProviderInterface. The same OpenAI / Claude clients used by agent runs are reused here. Token accounting is recorded per call.
10.4 Policy
AiTextTransformPolicy enforces:
- User must have token ability
ai:text:transform.
- If
surface.entity_typeis provided, the user must be able toviewthat entity. Otherwise the call is rejected.
- Selection length: 1 char ≤ len ≤ 16,000 chars. Larger → 422 with
code: selection_too_large.
- Total context length is capped at 24,000 chars; excess is truncated symmetrically.
11. Security
- All endpoints require Sanctum auth and the
ai:text:transformability.
- CSRF is not required for the SSE stream (token-bearer auth only).
- Secrets/PII scrubber: regex-based pre-flight strips obvious tokens (
sk-...,ghp_..., AWS keys, JWTs) from selection + context before sending to the provider. Hits are logged withoutcome=error, error_code=secret_redactedand the user sees a clear message.
- Per-project denylist of fields (defined in
agent_settings) that may not be transformed (e.g. legal text).
- All transformations are audited in
ai_text_transformationswith the requesting IP + user-agent inmetadata(added in a follow-up migration if needed).
12. iPhone parity
The iPhone app exposes the same feature via:
- Long-press → "Ask AI" in the system menu on any text view that wraps
AiSelectableText.
- Cmd+K on iPad keyboards.
- Same
POST /api/ai/text/transformendpoint; SwiftUI usesURLSessionSSE.
- Same surface envelope; the iOS client fills
client: "ios".
The CI parity test (introduced in page 18) is extended to assert that every Control Center surface in §1 has a SwiftUI counterpart with AiSelectableText enabled, or is on the explicit mobile-omitted list.
13. Configuration
New keys in agent_settings:
ai_text.enabled = true
ai_text.default_provider = openai
ai_text.default_model = gpt-4.1
ai_text.default_translate_lang = en
ai_text.max_selection_chars = 16000
ai_text.max_context_chars = 24000
ai_text.rate_per_user_per_min = 30
ai_text.rate_per_token_per_min = 6
ai_text.retention_days = 90
ai_text.field_denylist = ["users.password", "agent_settings.value"]14. Telemetry
Emit agent_events with event_type=ai_text_transform and severity info on every transformation (success / discarded / error). This makes the feature visible in the same live console as the rest of the system. Payload contains: surface_kind, action, provider, model, input_tokens, output_tokens, latency_ms, outcome.
15. Acceptance criteria
- From any of the 28 surfaces in §1, selecting at least 1 char shows the floating toolbar within 80 ms of selection end.
Cmd+K/Ctrl+Kopens the toolbar on the current selection on every surface in §1.
- The same envelope shape is sent for every surface; backend tests assert all 28
surface_kindvalues are present in fixtures.
- SSE streams partial tokens; first delta arrives in < 1.5 s p95 on
gpt-4.1.
- On editable surfaces, Replace writes the new value through the correct apply adapter and triggers the host editor's normal save path (Livewire/Alpine/Monaco/contenteditable/input all covered by unit tests).
- On read-only surfaces, Replace/Insert are hidden; Copy/Discard remain.
- Every transformation is persisted in
ai_text_transformationswith full context, outcome, tokens, latency.
- Rate-limit excess returns
429withRetry-After; the toolbar shows a non-blocking inline warning.
- Secrets scrubber blocks at least the patterns listed in §11; blocked attempts are visible in the audit log.
- iPhone client invokes the same endpoint and applies results through
AiSelectableTexton every parity-required surface.
- Adding a new text surface requires only adding the three
data-ai-*attributes — no per-surface JS, no per-surface controller.
- Disabling the feature (
ai_text.enabled = false) hides the toolbar globally and short-circuits the API with503 feature_disabled.
15.5 · Failure modes, fallbacks & graceful degradation
The selection editor touches every text surface in the app; a transient backend issue must never break the surface it overlays. The matrix below maps every failure mode to its fallback so the host editor stays usable in every weather. Every fallback is exercised in CI via fault-injection tests in tests/Feature/AiSelectionFallbackTest.php.
| Failure mode | User-visible effect | Fallback behaviour | Telemetry |
|---|---|---|---|
| Backend 5xx | Toolbar shows "AI is unavailable — try again in a moment." | Selection preserved; host editor remains fully editable; autosave unaffected. | agent_events severity=error, code=ai_text.backend_5xx. Aggregated to dashboard. |
| Rate limited (429) | Inline non-blocking toast with Retry-After countdown. | Toolbar disabled for the countdown; other surfaces unaffected; user can still type. | outcome=error, error_code=rate_limited. If sustained, prompt for plan upgrade. |
| Provider timeout (>30s) | Stream cancelled; toolbar shows "Result took too long." | Partial result kept in preview panel; Copy still works; Try again offered. | outcome=error, error_code=provider_timeout. Switch to fallback provider on next try if configured. |
| Selection too large (>16k chars) | Pre-flight 422; toolbar shows "Selection too long — narrow it." | No request sent; no cost incurred; suggestion shown to split selection into paragraphs. | outcome=error, error_code=selection_too_large. |
| Secrets scrubber match | Toolbar shows "This selection contains what looks like a secret. Edit and retry." | Request blocked at the edge; redacted preview shown to user only; never sent to provider. | outcome=error, error_code=secret_redacted • audit row to activity_logs. |
| Apply adapter missing | Replace / Insert hidden; Copy / Discard remain. | Host surface gracefully degrades to read-only AI assist; user can still use AI to think, just not auto-write. | Warning emitted to browser console; spec-coverage-lint flags the surface for the owner. |
| WebSocket / SSE drop | Stream switches to polling fallback transparently. | Result still arrives; latency p95 ≤ 6 s instead of ≤ 1.5 s; user sees a small "reconnecting" dot. | agent_events info, code=ai_text.stream_fallback. |
Feature disabled (ai_text.enabled=false) | Toolbar never appears; Cmd+K is a no-op. | Endpoint returns 503 feature_disabled; iPhone client hides menu item. | One audit row per disable toggle. Dashboard banner visible to admins. |
| Provider key rotated mid-stream | Stream interrupted at token boundary. | Frontend retries once with the new key; if second attempt fails, user sees standard timeout fallback. | agent_events warning, code=ai_text.key_rotation_interrupt. |
| Browser offline | Toolbar shows "You're offline." | Request queued in localStorage; replays on reconnect within 60 s, else discarded with a warning. | Client-side only; no server row created. |
Design intent. The toolbar is additive: removing it must never remove a capability the user already had. Every fallback above guarantees that property.
Recovery affordance. Every error state offers exactly one primary action: "Try again." Repeated failures (3+ in 60 s on the same surface) escalate to "Report this" which posts a structured payload (sanitized — never the selection text) to agent_events for the operator. The user sees a confirmation; the report is reviewed by the developer-tooling lead within one business day.
Provider failover policy. If the primary provider returns 5xx three times in 60 s, the service marks it unhealthy for 5 min and routes new requests to the configured fallback (e.g. OpenAI → Anthropic or vice versa). Health auto-recovers on a successful probe. Failover is invisible to the user except for a subtle model badge change in the preview header.
16. Open questions
- Translate target-language picker default: per-user vs. per-surface remembered.
- Should
transform(free-form) be gated behind an explicit per-token ability, e.g.ai:text:custom_prompt?
- For very long Markdown documents, do we stream context in chunks (better) or truncate (simpler)? Defaulting to truncate in v1.
- Undo: do we offer a one-click "undo Apply" that reads from
ai_text_transformationsand writes the pre-image back? Probably v1.1.
- Should we expose the prompt being sent to the model in a "Show prompt" debug panel for power users?
17. Build prompt cross-reference
This spec is wired into:
- B1 — adds
ai_text_transformationsmigration,AiTextTransformService,AiTextTransformController, policy, token abilityai:text:transform.
- B5 (Control Center) — adds the global mount in
layouts/app.blade.php, theai-text-editor.jspackage, the five apply adapters, the FilamentHasAiTextEditortrait, and updates the base form components.
- B6 (iPhone) — adds
AiSelectableTextSwiftUI wrapper and the parity contract test.
- Page 14 (API) — adds the three endpoints in §7.
- Page 15 (Security) — adds the scrubber + denylist rules.
- Page 18 (Control Center) — extends the parity matrix with
ai_text.surfaces[].
Any new feature page or build prompt that introduces a text surface MUST list it in §1 and mark it on the apply-adapter table.
18. docs/AI_SELECTION_EDITOR.md (ready-to-mirror body)
Drop the block below into docs/AI_SELECTION_EDITOR.md. It is a condensed mirror of this spec — the canonical, exhaustive version remains this Notion page. The doc body is the source-in-repo for Claude Code, Cursor, and the in-app RAG indexer.
# AI_SELECTION_EDITOR
Status: Planned
Last reviewed: 2026-05-11
Source: Notion F1 (master hub).
## What it is
A Notion-AI-style selection overlay that activates on every text surface in the Laravel Control Center and the iPhone app. Select → Ask AI → preview → Apply. Default: ON.
## Surfaces (default ON)
project descriptions · task descriptions · task comments · notes · agent prompts (run composer) · agent run summaries · AI-generated responses · AI output review areas · technical specifications · documentation editor (Markdown) · markdown editor (generic) · changelog editor · project map docs · database schema docs · architecture docs · API docs · server params descriptions · Git workflow docs · RAG docs · Claude Code docs · OpenAI docs · LiteSpeed / CentOS deployment docs · web app plan · iPhone app plan · settings descriptions · admin notes · review screens · approval screens.
Opt-out: add `data-ai-editor="off"` to the wrapper. Forced off on password / token / secret / API-key inputs by base form components.
## Actions (v1)
rewrite · improve · translate · simplify · expand · summarize · transform · continue · fix · shorten · lengthen · tone · explain · to_checklist · to_table · custom.
## API
- `POST /api/v1/ai/text/transform` — SSE stream; ability `ai:text:transform`; rate-limited 30/min/user, 6/min/token.
- `GET /api/v1/ai/text/transformations/{id}` — single record.
- `POST /api/v1/ai/text/transformations/{id}/rerun` — with optional `action` / `params` override.
## Frontend
Mount once in `resources/views/layouts/app.blade.php` (`<div id="ai-text-editor-root">` + Vite asset). Surfaces opt in via three `data-ai-*` attributes: `data-ai-surface`, `data-ai-entity-type`, `data-ai-entity-id`. Apply adapters: `livewire`, `alpine`, `monaco`, `contenteditable`, `input`.
## Backend
- Service: `App\Services\AiTextTransformService` — only caller of OpenAI / Anthropic for F1.
- Prompt builder: `App\Services\AiTextPromptBuilder` (versioned prompts in `config/agent_workspace/ai_text_prompts.php`).
- Policy: `App\Policies\AiTextTransformPolicy` (token ability + entity-view check + length limits).
- Persistence: `ai_text_transformations` table (see `docs/DATABASE_SCHEMA.md`).
## Security
- Secrets/PII pre-flight scrubber rejects requests where the selection or context matches `sk-…`, `ghp_…`, AWS keys, JWTs.
- Per-project denylist of fields (e.g. legal text) in `agent_settings.ai_text.field_denylist`.
- All transformations audited in `ai_text_transformations` with `outcome ∈ {returned, applied, discarded, error}`.
## iPhone parity
`AiSelectableText` SwiftUI wrapper. Same endpoint. Same envelope. `client="ios"`. CI parity test asserts every Control Center surface has a SwiftUI counterpart or is on the explicit mobile-omitted list.
## Telemetry
Emit `agent_events` with `type=ai_text_transform`, severity `info`, on every transformation.
## Acceptance criteria
See F1 §15 in the Notion spec pack.
<!-- AUTO-GENERATED:START -->
<!-- AUTO-GENERATED:END -->19. Consolidation note
When tempted to add a new Notion page for an F1-related sub-feature, don't. Add it to the surface list in §1, document the apply adapter in §9, and (if needed) extend the action catalog in §4. The repo doc body in §18 above is regenerated from this page; any in-repo file change must be reflected in this page first.
18. docs/AI_SELECTION_EDITOR.md (ready-to-mirror body)
Drop the block below into docs/AI_SELECTION_EDITOR.md. It is a condensed mirror of this spec — the canonical, exhaustive version remains this Notion page. The doc body is the source-in-repo for Claude Code, Cursor, and the in-app RAG indexer.
# AI_SELECTION_EDITOR
Status: Planned
Last reviewed: 2026-05-11
Source: Notion F1 (master hub).
## What it is
A Notion-AI-style selection overlay that activates on every text surface in the Laravel Control Center and the iPhone app. Select → Ask AI → preview → Apply. Default: ON.
## Surfaces (default ON)
project descriptions · task descriptions · task comments · notes · agent prompts (run composer) · agent run summaries · AI-generated responses · AI output review areas · technical specifications · documentation editor (Markdown) · markdown editor (generic) · changelog editor · project map docs · database schema docs · architecture docs · API docs · server params descriptions · Git workflow docs · RAG docs · Claude Code docs · OpenAI docs · LiteSpeed / CentOS deployment docs · web app plan · iPhone app plan · settings descriptions · admin notes · review screens · approval screens.
Opt-out: add `data-ai-editor="off"` to the wrapper. Forced off on password / token / secret / API-key inputs by base form components.
## Actions (v1)
rewrite · improve · translate · simplify · expand · summarize · transform · continue · fix · shorten · lengthen · tone · explain · to_checklist · to_table · custom.
## API
- `POST /api/v1/ai/text/transform` — SSE stream; ability `ai:text:transform`; rate-limited 30/min/user, 6/min/token.
- `GET /api/v1/ai/text/transformations/{id}` — single record.
- `POST /api/v1/ai/text/transformations/{id}/rerun` — with optional `action` / `params` override.
## Frontend
Mount once in `resources/views/layouts/app.blade.php` (`<div id="ai-text-editor-root">` + Vite asset). Surfaces opt in via three `data-ai-*` attributes: `data-ai-surface`, `data-ai-entity-type`, `data-ai-entity-id`. Apply adapters: `livewire`, `alpine`, `monaco`, `contenteditable`, `input`.
## Backend
- Service: `App\Services\AiTextTransformService` — only caller of OpenAI / Anthropic for F1.
- Prompt builder: `App\Services\AiTextPromptBuilder` (versioned prompts in `config/agent_workspace/ai_text_prompts.php`).
- Policy: `App\Policies\AiTextTransformPolicy` (token ability + entity-view check + length limits).
- Persistence: `ai_text_transformations` table (see `docs/DATABASE_SCHEMA.md`).
## Security
- Secrets/PII pre-flight scrubber rejects requests where the selection or context matches `sk-…`, `ghp_…`, AWS keys, JWTs.
- Per-project denylist of fields (e.g. legal text) in `agent_settings.ai_text.field_denylist`.
- All transformations audited in `ai_text_transformations` with `outcome ∈ {returned, applied, discarded, error}`.
## iPhone parity
`AiSelectableText` SwiftUI wrapper. Same endpoint. Same envelope. `client="ios"`. CI parity test asserts every Control Center surface has a SwiftUI counterpart or is on the explicit mobile-omitted list.
## Telemetry
Emit `agent_events` with `type=ai_text_transform`, severity `info`, on every transformation.
## Acceptance criteria
See F1 §15 in the Notion spec pack.
<!-- AUTO-GENERATED:START -->
<!-- AUTO-GENERATED:END -->19. Consolidation note
When you are tempted to "add a new Notion page" for an F1-related sub-feature, don't. Add it to the surface list in §1, document the apply adapter in §9, and (if needed) extend the action catalog in §4. The repo doc body in §18 above is regenerated from this page; any in-repo file change must be reflected in this page first.