Files
kandev/docs/plans/plugin-voice-extraction-host/plan.md
T
2026-08-12 20:29:18 +01:00

9.0 KiB

spec, created, status
spec created status
docs/specs/plugins/voice-extraction-host.md 2026-08-11 complete

Implementation Plan: Voice Plugin Host Prerequisites

Overview

Add host-owned composer adapters to every Voice Mode surface, then extend existing webhooks with per-declaration access and request-size policies. Publish the contract through frontend types, manifest validation, the frozen plugin API, and public authoring documentation. Keep core Voice Mode installed throughout; a separate plugin repository will consume these APIs and only a later parity task may remove core code.

Contract Decisions

  • Composer state stays local to each mounted surface. Slot props expose current disabled/submittable state plus insertion, focus, and native submit without exposing draft contents or private refs.
  • chat-input-actions grows compatible props and covers task chat plus Quick Chat. Creation forms get explicit task-create-input-actions and new-session-input-actions slots because their identifiers, validation, and submit lifecycles differ.
  • Plugin UI continues to use host.api.fetch("webhooks/<key>") and the existing HandleWebhook RPC. webhooks[].access selects public or authenticated invocation and max_body_bytes sets a bounded per-key limit up to 16 MiB; old manifests retain public/4 MiB defaults.
  • Plugin localization is a separate prerequisite. It must be resolved before core Voice Mode is removed, but this package adds no host i18n API.

Frontend Composer Architecture

  • Add public types in apps/web/lib/plugins/types.ts and mirror them in docs/plans/plugins/PLUGIN-API.md. Implement adapters guarded by the mounted composer generation; normal React props update disabled/submittable presentation without draft state entering Zustand.
  • Extend TipTapInputHandle only where required for the host adapter. Reuse its selection-aware insertText, but centralize the Voice spacing rule so textarea and TipTap adapters behave identically.
  • Adapt useChatInputState/ChatInputArea to expose native handleSubmit and the same disabled gate to ChatInputPluginActions. Do not call useMessageHandler or construct ChatSubmitPayload from plugin code. Quick Chat inherits the adapter through its existing ChatInputArea composition.
  • Extend TaskFormInputsHandle with guarded focus/insertion operations or provide a sibling adapter around useDescriptionInput. Task creation invokes its existing form submit handler; new-session creation invokes its existing handleSubmit, including profile, upload, and busy-state gates.
  • Keep slot controls in the existing toolbars on desktop. On native mobile, use the existing composer toolbar/overflow composition and 44px touch targets; do not introduce a separate drawer solely for plugin actions.

Mobile Design Contract

  • Outcome and entry points: Voice plugin actions remain beside native composer controls in task chat and inside the prompt toolbar for task/new-session creation; Quick Chat uses its existing input toolbar. All actions are reachable by touch.
  • Exemplars: task chat reuses chat-input-toolbar-mobile.tsx and mobile-voice-mode.spec.ts; new session reuses the full-height dialog in mobile-new-session-dialog.spec.ts; task creation reuses the existing responsive Task Create dialog.
  • Hierarchy and action: the plugin action is secondary to the native submit action and never hides submit, model/profile selection, or cancellation. Recording/transcription state belongs to the plugin control; the native composer remains the single draft and submit owner.
  • Surface rationale: frequent, one-tap input augmentation belongs inline in the existing toolbar, not behind navigation or a new drawer. Existing dialog and composer scroll owners remain unchanged; overlays and toolbars keep safe-area clearance and no document horizontal overflow.
  • Shared behavior: capability state and native adapters are shared across presentations. Only the existing toolbar placement differs. Pixel 5 Playwright proves insertion plus native submission in task chat, task creation, and new-session creation; Quick Chat mobile coverage is included if its shipped mobile entry exposes the full toolbar.

Backend Authenticated Webhook Architecture

  • Extend manifest Webhook with Access and MaxBodyBytes. Validate access as public|authenticated, default it to public, default the limit to 4 MiB, reject non-positive or

    16 MiB values, and preserve those values through install/registry serialization.

  • Keep the existing webhook URL and HandleWebhook RPC. In the handler, resolve the declared key before reading the body, apply its effective limit, and require normal Kandev identity plus accepted Origin for authenticated cookie requests. Authentication-disabled mode uses the synthetic user.
  • Propagate Request.Context() cancellation through Service.InvokeWebhook; bound and sanitize the response as today. Logs may add access mode and byte count but never request content or secrets.
  • Keep existing public webhooks byte-for-byte compatible: no authentication requirement and a 4 MiB default. The Voice plugin stores its OpenAI key through existing secret config fields; no host key storage or new secret API is introduced.

Localization And Compatibility

  • Add optional manifest fields without changing plugin api_version: 1. Document that consumers must declare the first supporting min_kandev_version; older hosts fail installation instead of falling back to webhooks.
  • Update docs/specs/plugins/spec.md, docs/plans/plugins/PLUGIN-API.md, docs/public/plugins-authoring.md, and docs/public/plugins-manifest.md in the same contract task.

Test Strategy

  • Frontend unit/component tests prove selection replacement, spacing, focus, live state props, stale-adapter failure, native submit delegation, blocked submission, cleanup, Quick Chat props, and desktop/mobile slot placement across all composer families.
  • Backend manifest tests prove defaults, validation, and compatibility. Handler/service tests prove authenticated/public access, Origin enforcement, 10 MiB success, >16 MiB rejection, cancellation, disabled/runtime failure, and unchanged legacy webhook behavior.
  • Desktop and mobile Playwright install or inject a fixture plugin action, insert a distinctive string at a non-terminal selection, invoke native submit, and assert the resulting task/message/session. A backend-integrated E2E action uploads >10 MiB only where the container/runtime fixture is available; focused Go integration tests remain the deterministic size/security proof.

Implementation Waves And Tasks

Wave 1:

Wave 2:

Wave 3:

Tasks in the same wave are parallel-safe only where their owned files remain disjoint. Execution stays sequential unless the user explicitly authorizes subagents.

Risks

  • A capability that snapshots React state can auto-submit after a gate changes; submit() must revalidate the native gate at call time.
  • React Strict Mode or a task/session switch can leave stale plugin closures alive; generation guards must make them fail closed rather than target the newly mounted editor.
  • Conditional authentication inside a route currently treated as public must fail closed per declared key; direct handler and middleware-boundary tests are required.
  • Raising gRPC message limits globally would unnecessarily affect other plugin traffic; configure only the existing webhook call path sufficiently for the 16 MiB host ceiling.
  • Core Voice removal in this package would make rollback and parity validation unsafe; it is explicitly deferred to the plugin implementation/extraction task.

Verification Commands

make -C apps/backend test
make -C apps/backend lint
cd apps && pnpm install --frozen-lockfile
cd apps && pnpm --filter @kandev/web test -- --run lib/plugins components/task/chat components/task-create-dialog-selectors.test.tsx components/task/new-session-dialog.test.tsx
cd apps/web && pnpm run typecheck
cd apps/web && pnpm run i18n:check && pnpm run i18n:ratchet
make build-web && make build-backend
cd apps/web && pnpm e2e:raw --project=chrome e2e/tests/plugins/composer-actions.spec.ts e2e/tests/chat/quick-chat.spec.ts e2e/tests/session/new-session-dialog.spec.ts
cd apps/web && pnpm e2e:raw --project=mobile-chrome e2e/tests/plugins/mobile-composer-actions.spec.ts e2e/tests/session/mobile-new-session-dialog.spec.ts
node --test scripts/validate-public-docs.test.mjs