Move task plugin actions and destinations into the shared mobile Plugins menu, preserving a compact task header and accessible touch targets. Add focused mobile coverage, plugin authoring documentation, and process-probe stability handling.
10 KiB
created, status, requirements, system_design, legacy_specs
| created | status | requirements | system_design | legacy_specs | ||
|---|---|---|---|---|---|---|
| 2026-09-22 | implemented |
|
|
Implementation Plan: Mobile Task Plugin Menu
Overview
Move task-scoped chat-top-bar plugin contributions out of the fixed phone task
header and into the shared app menu's existing Plugins section. First add the
responsive slot contract and host composition with component regressions, then
prove the result with the packaged plugin fixture on the managed Pixel 5 project
and capture the two phone states needed for the PR.
Confirmed root cause
SessionMobileTopBar renders TaskTopBarPluginActions inside
MobileTopBarActions, whose shrink-0 cluster also owns the hamburger. Every
registered chat-top-bar component is opaque and rendered inline, while
ChatTopBarSlotProps has no presentation field or phone containment contract.
The supplied 390-CSS-pixel phone screenshot shows two contributions consuming
the middle of the fixed row and reducing the title/branch region to a cramped
remainder. The code path is deterministic even though the document itself does
not overflow.
The closest shipped correction is the listing main-top-bar slot: its phone
presentation leaves the persistent header, renders in the shared app menu, and
receives presentation: "mobile". The task slot should follow that boundary
while preserving task and session identity.
Scope
In scope
- Remove
chat-top-barplugin components from the persistent phone task header. - Render those components inside the shared phone menu's existing Plugins group.
- Add a public desktop/mobile presentation field without changing existing task, workspace, active-session, or session-list context.
- Contain multiple and long contributions in the menu and preserve 44px phone controls supplied through host UI primitives.
- Keep tablet and desktop task-top-bar placement unchanged.
- Add component and managed mobile E2E regressions using the real packaged plugin fixture.
- Capture a synthetic long-title header and the opened Plugins menu for the PR.
Out of scope
- Moving first-party approval, archive, port-forwarding, executor, or change-request controls.
- Changing plugin registration lifecycle, backend APIs, persistence, or permissions.
- Redesigning plugin navigation or the shared menu hierarchy outside the combined Plugins section.
- Changing listing
main-top-barbehavior.
Technical approach
Public slot contract
- Export
ChatTopBarSlotPropsfromapps/packages/plugin-sdk/src/index.tsand use it as the canonical host type inapps/web/components/task/task-top-bar-plugin-actions.tsx. - Add
presentation: "desktop" | "mobile". Default the host component to desktop so existing call sites and plugin behavior remain compatible. - Update
docs/plans/plugins/PLUGIN-API.md,docs/public/plugins-authoring.md, and the SDK exact-consumer test with the phone placement, containment, and touch contract.
Phone composition
- Add a reactive registration-presence helper beside
TaskTopBarPluginActions; it prevents the app menu from rendering an empty Plugins group when nochat-top-barslot is registered. - Give
TaskTopBarPluginActionsa mobile wrapper that uses the available width, wraps contributions, constrains direct children, and normalizes host buttons to a minimum 44px active target. Keep its desktop return path unchanged. - Remove the plugin slot from
MobileTopBarActions. Pass the mobile presentation throughAppNavSheetinstead, with the same task, workspace, active-session, and task-session IDs. - Thread the optional content through
AppNavSectionsintoMobilePluginNavSection. Render it above plugin destination rows under one localized Plugins heading, and keep the section absent when it has neither contributions nor destinations. - Preserve the menu's existing single vertical scroll owner, safe-area padding, link dismissal, Escape behavior, and focus return. A plugin control owns its interaction and does not close the menu merely because it was activated.
Regression and PR evidence
- Extend the packaged E2E plugin with deterministic
chat-top-barcontributions that expose the received presentation and include compact and wider content. - Extend
apps/web/e2e/tests/plugins/mobile-plugin-topbar.spec.tswith a task scenario that creates a synthetic long-title task, verifies the fixed header and menu geometry, activates a plugin control, and checks zero document horizontal overflow. - Use
prCapturein that scenario to writemobile-task-plugin-header.pngandmobile-task-plugin-menu.png. The closed screenshot shows recovered task hierarchy; the open screenshot shows where plugin controls moved. Validate both manifest entries before PR publication.
ASCII UI preview
UI-01: Phone task header with plugin contributions
Entry point: /t/:taskId below md, with two chat-top-bar registrations.
Current behavior confirmed by the supplied screenshot and source trace:
+--------------------------------------------------+
| < | Long task title... v | [plugin] [status] | = |
| | branch +changes | | |
+--------------------------------------------------+
^ task identity is squeezed by an unbounded,
non-shrinking plugin cluster
Target persistent header:
+--------------------------------------------------+
| < | Long task title with useful context... v | = |
| | repo / branch +changes | |
+--------------------------------------------------+
fixed row; plugin contributions are absent
first-party contextual controls may remain before the menu
Target opened shared menu:
+--------------------------------------------------+
| Menu x |
|--------------------------------------------------|
| Workspace |
| [ Current workspace v ] |
| |
| Home [Quick Chat] [Terminal] |
| Tasks |
| ... existing task navigation ... |
| |
| Plugins |
| [ plugin action ] [ session status 23% ] |
| [ additional controls wrap inside the menu ] |
| [ plugin destination row ] |
| |
| Integrations |
| Utilities |
+--------------------------------------------------+
one vertical scroll owner; no horizontal page scroll
Structural requirements: the phone header keeps task identity as the flexible
region; the hamburger remains fixed and touch reachable; plugin contributions
share one Plugins section, wrap within its width, and give host.ui.Button
controls a 44px active target. The exact spacing, labels inside plugin-owned
components, and number of first-party header controls remain governed by current
design tokens and availability.
Tablet and desktop keep the current inline task top bar:
Task identity | metrics | plugin contributions | status/tools/actions
This preview maps to AC-UI-MOBILE-TASK-CHROME-001.5, .6, and .7.
Tests
AC-UI-MOBILE-TASK-CHROME-001.7:apps/web/components/task/mobile/session-mobile-top-bar.test.tsxfirst recorded the fixed-header placement failure. The task top-bar tests then prove the desktop/mobile context, containment wrapper, and reactive presence contract.AC-UI-MOBILE-TASK-CHROME-001.5and.7:apps/web/components/task/mobile/session-mobile-top-bar.test.tsxproves the plugin slot is not a child of the fixed action cluster and is supplied to the shared menu only while registrations exist.AC-UI-MOBILE-TASK-CHROME-001.7:apps/web/components/plugins/mobile-plugin-nav-section.test.tsxandapps/web/components/navigation/app-nav-sheet.test.tsxprove the combined Plugins group, empty behavior, ordering, and context.AC-UI-MOBILE-TASK-CHROME-001.6and.7:apps/web/lib/plugins/sdk-contract.test.tsand the plugin SDK typecheck prove one public structural contract and unchanged desktop presentation.
E2E tests
AC-UI-MOBILE-TASK-CHROME-001.5,.6, and.7:apps/web/e2e/tests/plugins/mobile-plugin-topbar.spec.tsinstalls the packaged fixture, opens a long-title task inmobile-chrome, proves the closed header retains usable non-overlapping title/menu geometry, opens the app menu, proves both plugin contributions are contained and receive the mobile presentation, activates one, and asserts no document horizontal overflow. The existing listing scenario remains in the same focused run.
Work orders
Verification results
- Installed the locked workspace dependencies.
- Focused Vitest suite: 49 tests passed across the task top bar, phone header, shared Plugins section, navigation sheet, and public SDK contract.
- Plugin SDK and web TypeScript checks passed; targeted ESLint completed with zero warnings.
- Managed
mobile-chromePlaywright run: 2 tests passed against the packaged fixture, including long-title geometry, context, 44px host-button size, activation, focus return, and horizontal-overflow assertions. - PR capture manifest contains the validated synthetic header and menu PNGs.
- Public-doc validation passed 70 tests and 47 published pages; specification indexing and lint passed 299 decisions and 1,108 specifications.
Risks
- Plugin components can render arbitrary markup. Host width constraints protect the menu, while the public presentation field lets cooperative plugins choose a clearer phone layout; the host cannot make opaque plugin copy meaningful.
- Moving the phone instance into the drawer changes when a plugin component is visibly mounted. The contract must avoid promising background polling or local disclosure state while the menu is closed.
- Extending the shared fixture affects every E2E test that installs it. Keep the new registrations inert outside task detail and run the existing listing scenario with the new task scenario.
- The user-provided screenshot contains live task data. PR captures must use the synthetic fixture task instead of publishing that attachment.