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 screens

If 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)

ActionKeyDefault modelNotes
Rewriterewritegpt-4.1Same meaning, different wording.
Improveimprovegpt-4.1Grammar + clarity + tone consistency.
Translatetranslategpt-4.1Target language picker; remembers last 3.
Simplifysimplifygpt-4.1Plain language, shorter sentences.
Expandexpandgpt-4.1Add detail without changing facts.
Summarizesummarizegpt-4.13-bullet default; toggle to 1-line / paragraph.
Transformtransformgpt-4.1Free-form natural-language instruction.
Continue writingcontinuegpt-4.1Editable surfaces only; uses cursor instead of selection.
Fix spelling/grammarfixgpt-4.1Minimal-diff edit.
Make shortershortengpt-4.1at most 60% of original length.
Make longerlengthengpt-4.1at least 140% of original length.
Change tonetonegpt-4.1Sub-picker: professional / friendly / concise / persuasive / neutral.
Explainexplaingpt-4.1Returns explanation, never replaces.
Convert to checklistto_checklistgpt-4.1Markdown task list.
Convert to tableto_tablegpt-4.1Best-effort Markdown table.
Customcustomper-user settingFree-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.kind is read from the nearest ancestor element with data-ai-surface="...". Required on every surface listed in §1.
  • surface.entity_type + surface.entity_id come from data-ai-entity-type / data-ai-entity-id. Used for audit and (when allowed) RAG retrieval.
  • surface.editable is false on read-only surfaces (AI responses, review screens, doc renderers).
  • surface.format is markdown (default), plain, or code. Affects the system prompt.
  • surrounding_before / surrounding_after come 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 via data-ai-context="page".
  • The frontend never sends payload_json of 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. AiTextTransformService is 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 into

The 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 generic setAiText(field, value) Livewire action on the nearest component.
  • alpine — writes to x-data property by ref.
  • monaco — uses the Monaco editor instance API on the wrapper.
  • contenteditable — replaces the live Range of the selection.
  • input — sets value of the closest <input> / <textarea> and dispatches input + change events.

9.4 Filament + Livewire base components

Update the base form components to forward the annotation automatically:

  • <x-agent::textarea> → emits data-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-apply absent.
  • Filament: a HasAiTextEditor field 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_type is provided, the user must be able to view that 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:transform ability.
  • 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 with outcome=error, error_code=secret_redacted and 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_transformations with the requesting IP + user-agent in metadata (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/transform endpoint; SwiftUI uses URLSession SSE.
  • 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

  1. From any of the 28 surfaces in §1, selecting at least 1 char shows the floating toolbar within 80 ms of selection end.
  1. Cmd+K / Ctrl+K opens the toolbar on the current selection on every surface in §1.
  1. The same envelope shape is sent for every surface; backend tests assert all 28 surface_kind values are present in fixtures.
  1. SSE streams partial tokens; first delta arrives in < 1.5 s p95 on gpt-4.1.
  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).
  1. On read-only surfaces, Replace/Insert are hidden; Copy/Discard remain.
  1. Every transformation is persisted in ai_text_transformations with full context, outcome, tokens, latency.
  1. Rate-limit excess returns 429 with Retry-After; the toolbar shows a non-blocking inline warning.
  1. Secrets scrubber blocks at least the patterns listed in §11; blocked attempts are visible in the audit log.
  1. iPhone client invokes the same endpoint and applies results through AiSelectableText on every parity-required surface.
  1. Adding a new text surface requires only adding the three data-ai-* attributes — no per-surface JS, no per-surface controller.
  1. Disabling the feature (ai_text.enabled = false) hides the toolbar globally and short-circuits the API with 503 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 modeUser-visible effectFallback behaviourTelemetry
Backend 5xxToolbar 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 matchToolbar 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 missingReplace / 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 dropStream 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-streamStream 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 offlineToolbar 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

  1. Translate target-language picker default: per-user vs. per-surface remembered.
  1. Should transform (free-form) be gated behind an explicit per-token ability, e.g. ai:text:custom_prompt?
  1. For very long Markdown documents, do we stream context in chunks (better) or truncate (simpler)? Defaulting to truncate in v1.
  1. Undo: do we offer a one-click "undo Apply" that reads from ai_text_transformations and writes the pre-image back? Probably v1.1.
  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_transformations migration, AiTextTransformService, AiTextTransformController, policy, token ability ai:text:transform.
  • B5 (Control Center) — adds the global mount in layouts/app.blade.php, the ai-text-editor.js package, the five apply adapters, the Filament HasAiTextEditor trait, and updates the base form components.
  • B6 (iPhone) — adds AiSelectableText SwiftUI 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.