paseo-follow-up 0.3.6 → 0.4.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.
- package/OVERVIEW.md +19 -7
- package/README.md +70 -79
- package/THIRD_PARTY_NOTICES.md +33 -0
- package/assets/LICENSE.lucide +43 -0
- package/assets/icon.png +0 -0
- package/assets/icon.svg +1 -0
- package/client/button-visibility.ts +2 -1
- package/client/chat.tsx +163 -0
- package/client/display.tsx +12 -17
- package/client/header.tsx +92 -0
- package/client/keys.ts +3 -0
- package/client/kit.tsx +119 -0
- package/client/large.tsx +58 -0
- package/client/live.tsx +159 -0
- package/client/motion.tsx +93 -0
- package/client/needs-you.tsx +121 -0
- package/client/overview.tsx +111 -16
- package/client/panel.tsx +257 -68
- package/client/pr-controls.tsx +50 -33
- package/client/pr-sheet.tsx +63 -0
- package/client/questions.tsx +307 -0
- package/client/settings.tsx +55 -20
- package/client/spec.tsx +113 -0
- package/client/stacks.tsx +128 -0
- package/client/tasks.tsx +113 -22
- package/client/theme.ts +69 -0
- package/docs/screenshots/follow-up-chat.png +0 -0
- package/docs/screenshots/follow-up-hero.png +0 -0
- package/docs/screenshots/follow-up-large.png +0 -0
- package/docs/screenshots/follow-up-mobile.png +0 -0
- package/docs/screenshots/follow-up-overview.png +0 -0
- package/docs/screenshots/follow-up-pill.png +0 -0
- package/docs/screenshots/follow-up-pr.png +0 -0
- package/docs/screenshots/follow-up-questions.png +0 -0
- package/docs/screenshots/follow-up-states.png +0 -0
- package/index.client.tsx +47 -16
- package/index.server.ts +53 -0
- package/package.json +4 -2
- package/paseo-plugin.json +11 -3
- package/server/agent-hook.mjs +2 -0
- package/server/daemon.ts +37 -6
- package/server/discovery.ts +6 -3
- package/server/engine.ts +39 -0
- package/server/hook-source.ts +1 -1
- package/server/mcp-source.ts +1 -1
- package/server/mcp.mjs +12 -2
- package/server/questions.ts +112 -0
- package/server/spec.ts +102 -0
- package/shared/activity.ts +56 -0
- package/shared/attention.ts +212 -0
- package/shared/chat.ts +130 -0
- package/shared/contracts.ts +21 -0
- package/shared/display.ts +2 -1
- package/shared/errors.ts +5 -0
- package/shared/model.ts +10 -0
- package/shared/pr-actions.ts +8 -1
- package/shared/questions.ts +123 -0
- package/shared/spec.ts +46 -0
- package/skills/follow-through/SKILL.md +4 -0
- package/client/components.tsx +0 -58
- package/client/navigation.tsx +0 -23
- package/docs/screenshots/follow-up-settings.png +0 -0
- package/docs/screenshots/follow-up-workspace.png +0 -0
package/OVERVIEW.md
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
# Follow-up
|
|
2
2
|
|
|
3
|
-
Follow-up
|
|
3
|
+
Follow-up watches every pull request a Paseo workspace opens until the work is actually done. It shows each PR stack at a glance, gathers conflicts, failing required checks and review threads into one repair batch for the workspace's main agent, and tells you when something needs you, such as an approved PR to merge, a question the agent asked or a batch to send. Answer a question in Follow-up and the answer goes straight to the agent's chat.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## What you see
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- A workspace panel that leads with status: PRs merged, what needs you, what needs fixing, a bar with one segment per PR, and what the agent is doing now.
|
|
8
|
+
- A Needs you card with one button per decision: Send repairs, Merge, Resume, Retry, or Answer.
|
|
9
|
+
- Questions from the main agent, each with a permanent ref (Q1, Q2…), lettered options, the agent's recommendation and what it does while it waits. Pick an option, add a note and Send it to the agent, or Copy it instead. Answered questions stay listed under their ref, and the question mode setting (Stop, Work around, Keep building) tells agents what to do until you answer.
|
|
10
|
+
- A Spec link in the header to the page that sums up the workspace, usually the Claude artifact the main agent keeps up to date.
|
|
11
|
+
- An Activity card: repairs sent and finished with how long they took, merges, PR actions and questions.
|
|
12
|
+
- Each stack drawn top to base like Graphite, merged PRs folded away, and a sheet per PR with checks, reviewers and Merge.
|
|
13
|
+
- Tasks for work that outlives a merge, manual or scheduled after a merge or deploy, with an optional delay.
|
|
14
|
+
- A composer pill and a sidebar count that appear only when something needs you; the pill steps through open questions so you can answer without leaving the chat. One screen lists every tracked workspace by who is up next, and a larger view opens a workspace in two columns.
|
|
15
|
+
- Compact rows for Follow-up's own tool calls in agent chats, a card for each repair batch, and a card when the agent declares Done.
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
## Setup
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
Requires Paseo 0.14+, Node 22+ and an authenticated `gh` on the daemon host. Merge uses saved Graphite CLI authentication. Open a workspace, open Follow-up from its header or Command-K, and choose Track this workspace. Project settings can track every workspace in a project. The optional Send to Paseo browser companion adds Graphite's Merge when ready switch through its existing pairing; it is not needed for anything else.
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
## What it does on its own, and what it never does
|
|
14
22
|
|
|
15
|
-
|
|
23
|
+
Auto-send starts off for every workspace. Until you turn it on, each repair batch waits for your click; with it on, Follow-up sends batches once the main agent is idle, after fresh permission and Session Guard checks. Follow-up never merges, turns on merge when ready, requests reviewers or archives on its own, and Merge always re-checks the stack and asks again. Done needs the main agent's declaration, settled PRs and tasks, and no open questions. Answers are sent only when you click Send; an answer the agent can't take yet waits and goes out on the next check, and one Paseo didn't confirm waits for you to Resend. An optional recovery agent can rebuild missed PRs and tasks from a workspace's history; it runs only when you start it and never edits code. Deployment checks and reviewer shortcuts exist only for specifically configured repositories; elsewhere deployment stays unknown.
|
|
24
|
+
|
|
25
|
+
## Data and access
|
|
26
|
+
|
|
27
|
+
This is a trusted, unsandboxed plugin. It reads workspace metadata, saved agent history and configuration, GitHub status and existing CLI authentication, and keeps its state in `plugin-data/follow-through` on the daemon host. It registers scoped MCP tools and reversible provider hooks only for tracked sessions. To find the spec link it reads completed Claude Artifact publish results in the workspace's root sessions, never page content, chat links or the repository; a link you set wins over the agent's, which wins over detection. Repair prompts go to your configured agent and provider. It has no Slack identity and no background channel scanning; replies to stakeholders need your authorization and a sent-message receipt from the agent.
|
package/README.md
CHANGED
|
@@ -1,126 +1,117 @@
|
|
|
1
|
-
#
|
|
1
|
+
# paseo-follow-up
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/paseo-follow-up)
|
|
4
4
|
[](https://www.npmjs.com/package/paseo-follow-up)
|
|
5
5
|
[](https://paseo.sh)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Follow-up watches every pull request a Paseo workspace opens until the work is actually done. It shows the whole stack at a glance, gathers conflicts, failing checks and review threads into one repair batch for the workspace's main agent, and tells you when something needs you: an approved PR to merge, a question the agent asked, or a batch to send. Answer a question in Follow-up and the answer goes straight to the agent's chat.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
<picture>
|
|
11
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/media/follow-up-hero-light.png">
|
|
12
|
+
<img alt="A Paseo workspace with the Follow-up panel open: a Spec link, 1 of 5 PRs merged, 4 need you, 2 to fix, a coloured stack bar, and a Needs you card with two questions from the agent, three repairs to send and Merge #418, beside the agent's chat" src="docs/screenshots/follow-up-hero.png">
|
|
13
|
+
</picture>
|
|
11
14
|
|
|
12
15
|
Screenshots use synthetic workspaces and PRs.
|
|
13
16
|
|
|
14
|
-
## Requirements
|
|
15
|
-
|
|
16
|
-
- Paseo 0.14.0 or newer and Node 22 or newer.
|
|
17
|
-
- Authenticated GitHub CLI (`gh`) on the daemon host.
|
|
18
|
-
- Saved Graphite CLI authentication for Graphite readiness and explicit Merge controls.
|
|
19
|
-
- Optional Send to Paseo browser companion for Graphite's actual Merge when ready switch. Core tracking and follow-up work without it.
|
|
20
|
-
|
|
21
17
|
## Install
|
|
22
18
|
|
|
23
19
|
```sh
|
|
24
20
|
paseo plugin install paseo-follow-up
|
|
25
21
|
```
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
Needs Node 22+ and an authenticated GitHub CLI (`gh`) on the daemon host. Merge buttons use your saved Graphite CLI authentication. The installed plugin ID stays `follow-through`, so existing settings and tracked work carry over.
|
|
28
24
|
|
|
29
25
|
## Use
|
|
30
26
|
|
|
31
|
-
Open
|
|
27
|
+
Open a workspace, click the Follow-up icon in its header (or press ⌘K / Ctrl+K and choose **Follow-up: open for this workspace**), then **Track this workspace**. To track every workspace in a project, open the settings (the gear in the panel, or **Follow-up: project settings** in ⌘K) and turn the project on.
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
The panel reads top to bottom:
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
- **Status.** How many PRs have merged, how many things need you, how many PRs need fixing, and a bar with one segment per PR in merge order. Below it, what the agent is doing right now.
|
|
32
|
+
- **Needs you.** One row per decision, each with its button: answer the agent's question, send repairs to the main agent, merge an approved PR, resume after you stopped the agent, or retry a batch that didn't finish.
|
|
33
|
+
- **Pull requests.** Each stack drawn top to base, the way Graphite shows it, with merged PRs folded away.
|
|
34
|
+
- **Tasks.** Work that outlives a merge, such as a production check or a backfill. Tasks wait for you unless you schedule them for after a merge or deploy.
|
|
35
|
+
- **Activity.** What happened and how long it took: repairs sent and finished, merges, merge and reviewer actions, and every question under its ref.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
The **Spec** chip in the header opens the page that sums up the workspace, usually the Claude artifact the main agent keeps up to date. The agent registers it with a tool call, Follow-up also picks up artifacts the workspace's sessions publish, and you can set, change or remove it under **Spec, agent and recovery**. Your link wins over the agent's, which wins over detection.
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+

|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
### Questions
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
When the main agent needs a decision it asks in Follow-up instead of burying it in chat. Each question gets a permanent ref (Q1, Q2…), lettered options with what each means, the agent's recommendation, and a line saying what the agent does while it waits. Pick an option, add a note if you like, and **Send to agent**: the answer goes straight into its chat, even mid-turn, and stays recorded under its ref. **Copy** puts the same answer on the clipboard instead. If the agent can't take it yet (Session Guard paused it, or a permission prompt is open) the answer waits and goes out on the next check; if Paseo doesn't confirm the message, Follow-up asks you to check before resending. A task the agent marks blocked becomes a question too.
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
The question mode in Follow-up's settings tells agents what to do until you answer: **Stop** (end the turn and wait), **Work around** (the default: build nothing the answer affects, keep working on the rest) or **Keep building** (build the recommendation and change course if you pick another). Big questions, ones that reach outside this computer or that most of the work depends on, always wait.
|
|
48
|
+
|
|
49
|
+
### Everywhere else
|
|
50
|
+
|
|
51
|
+
When a workspace needs you, a pill appears above its main agent's message box with the same actions. It steps through open questions ("Question 1 of 2") so you can answer without leaving the chat. The sidebar's Follow-up row counts how many workspaces are waiting, and nothing shows when nothing needs you.
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
Tap a PR, or its segment in the bar, for a sheet with its checks, reviewers and Merge controls. When Merge isn't available yet, the sheet says what it is waiting for.
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+
The expand icon next to the gear (or **Follow-up: open the larger view** in ⌘K) opens the workspace as a full screen, in two columns when there is room: what needs you on the left, the stacks and activity on the right. Esc or the collapse icon takes you back.
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
registered a PR here. New registrations record that agent; older records retain the workspace’s
|
|
47
|
-
main agent. You can still choose a fresh agent in Send to Paseo. Tracking and follow-up remain
|
|
48
|
-
usable without the companion.
|
|
61
|
+

|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
Follow Up 0.3.4+, and keep Graphite open and signed in. Follow Up connects automatically through
|
|
52
|
-
your existing Send to Paseo pairing and advertises the repositories of active tracked PRs.
|
|
63
|
+
In the agent's chat, Follow-up's own tool calls collapse into one-line rows, your answers and repair batches show as short cards instead of raw prompts, and the agent's Done call gets a card of its own.
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
future changes from that machine for the exact repository. Revoke that permission or turn off
|
|
56
|
-
browser access in extension settings → **Follow Up**. This is a PR setting, including later pushes.
|
|
57
|
-
See the [integration and security contract](https://github.com/tomgrin10/send-to-paseo/blob/main/docs/follow-up-integration.md).
|
|
65
|
+

|
|
58
66
|
|
|
59
|
-
|
|
67
|
+
**Follow-up: all tracked workspaces** (or the sidebar row) lists every tracked workspace, grouped by who's up next, with each stack's bar and its Spec link.
|
|
60
68
|
|
|
61
|
-
|
|
69
|
+

|
|
62
70
|
|
|
63
|
-
|
|
71
|
+
On a phone the panel opens as a tab, and the PR sheet and pill popover open as bottom sheets.
|
|
64
72
|
|
|
65
|
-
|
|
73
|
+

|
|
66
74
|
|
|
67
|
-
##
|
|
75
|
+
## How it works
|
|
76
|
+
|
|
77
|
+
- The workspace's main agent owns its PRs, tasks, questions, the spec link and the Done call. Other agents in the workspace can register PRs and check status.
|
|
78
|
+
- **Auto-send** is off for every newly tracked workspace, so each repair batch waits for your click. Turn it on per workspace and Follow-up sends batches itself once the agent is idle, after fresh permission and Session Guard checks.
|
|
79
|
+
- Follow-up never merges, turns on merge when ready, requests reviewers or archives on its own. **Merge** re-checks the stack, lists any PRs underneath that would merge first, and asks again.
|
|
80
|
+
- A merged PR is not the end: tasks such as production checks and open questions keep the workspace open until the main agent declares Done and everything has settled.
|
|
81
|
+
- Answers go out only when you click Send. Follow-up never answers for you.
|
|
82
|
+
|
|
83
|
+
## Send to Paseo and Graphite
|
|
84
|
+
|
|
85
|
+
With [Send to Paseo](https://github.com/tomgrin10/send-to-paseo) on the same daemon, its composer picks the agent that registered the PR. With Send to Paseo 2.2+, Follow-up can also toggle Graphite's **Merge when ready** through your existing pairing, while Graphite is open and signed in. Each change is confirmed in Chrome, which can remember the permission per repository. See the [integration and security contract](https://github.com/tomgrin10/send-to-paseo/blob/main/docs/follow-up-integration.md).
|
|
86
|
+
|
|
87
|
+
## Data and access
|
|
88
|
+
|
|
89
|
+
Paseo plugins run trusted, unsandboxed code on the daemon host. Follow-up reads workspace metadata, agent history and GitHub PR status with your existing credentials, and keeps its state in `$PASEO_HOME/plugin-data/follow-through`. It registers scoped MCP tools and reversible provider hooks only for tracked sessions. GitHub and Graphite writes happen only after an explicit click and a fresh eligibility check.
|
|
90
|
+
|
|
91
|
+
## Development
|
|
68
92
|
|
|
69
93
|
```sh
|
|
70
94
|
npm ci
|
|
71
95
|
npm run verify
|
|
72
96
|
npm run test:install
|
|
73
|
-
|
|
97
|
+
paseo plugin reload follow-through # after changes
|
|
74
98
|
```
|
|
75
99
|
|
|
76
|
-
Integration checks use a separate Paseo home
|
|
77
|
-
|
|
78
|
-
## Releases
|
|
100
|
+
Integration checks use a separate Paseo home with synthetic agents. Version tags (`vX.Y.Z`) publish to npm through Trusted Publishing.
|
|
79
101
|
|
|
80
|
-
|
|
102
|
+
## License
|
|
81
103
|
|
|
82
|
-
|
|
104
|
+
[MIT](LICENSE) © 2026 Tom Gringauz.
|
|
83
105
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Requires Paseo **0.14.0 or newer**. Agent tools are scoped to interactive Claude
|
|
87
|
-
and Codex sessions; history-only opens receive none. They become available on
|
|
88
|
-
the next idle session open after install or reload. The agent label
|
|
89
|
-
`plugin-tools/enabled: "false"` opts a session out. Existing plugin data and
|
|
90
|
-
settings are retained.
|
|
106
|
+
Questions, question modes and the activity log are adapted from [Progress](https://github.com/stevecastaneda/paseo-plugins/tree/main/progress) by Steve Castaneda (MIT). Its notice is in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
|
91
107
|
|
|
92
108
|
## Other plugins
|
|
93
109
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
- [Defer](https://github.com/tomgrin10/paseo-defer) — Schedule messages to agents for later delivery.
|
|
97
|
-
- [Graphite](https://github.com/tomgrin10/paseo-graphite) — Workspace stack status and PR action radar.
|
|
110
|
+
- [Defer](https://github.com/tomgrin10/paseo-defer) — Schedule messages for later delivery.
|
|
98
111
|
- [Send to Paseo](https://github.com/tomgrin10/send-to-paseo) — Send GitHub and Graphite PRs to Paseo from Chrome.
|
|
99
|
-
- [
|
|
100
|
-
- [
|
|
101
|
-
- [
|
|
102
|
-
- [Transfer](https://github.com/tomgrin10/paseo-transfer) — Copy or move workspaces
|
|
103
|
-
- [
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Successful `gt submit` and `gh pr create` receipts in root-session history also discover submitted
|
|
107
|
-
PRs automatically, including from secondary git worktrees. Discovery checks the workspace's GitHub
|
|
108
|
-
repository identity; merely mentioned PRs and read-only commands do not establish ownership.
|
|
109
|
-
Explicit registrations, exclusions and another tracked workspace's ownership take precedence.
|
|
110
|
-
Observed GitHub base/head branches supply inferred stack relationships; explicit edits override them.
|
|
111
|
-
Tracking continues after the checkout branch's PR merges, and the submitting session remains the
|
|
112
|
-
recipient for Send to Paseo. Existing sessions can use the hook-provided CLI fallback before their
|
|
113
|
-
next MCP session refresh. Automatic discovery does not transfer main-agent authority or enable Auto.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
Agents launched by another agent into a separate workspace can be that new workspace’s main agent.
|
|
117
|
-
The creator’s parent-agent label records launch provenance; it excludes an agent from local main
|
|
118
|
-
selection only when the parent belongs to the same workspace. Parent membership is checked from
|
|
119
|
-
the daemon, including direct lookup when the creator is no longer in the live directory. Unknown
|
|
120
|
-
membership does not grant new scope. Workspace-first creation is supported: tracking waits for an
|
|
121
|
-
available local main, then applies the project default. Explicit Track also supports these launches.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
Track persists activation and returns without waiting for transcript scans or the daemon-wide PR
|
|
125
|
-
refresh. Initial session attachment also uses ownership metadata only. PR/history discovery and
|
|
126
|
-
status refresh continue in the background; a slow GitHub or agent observation does not hold Track.
|
|
112
|
+
- [Session Guard](https://github.com/tomgrin10/paseo-session-guard) — Pause at session limits and continue after reset.
|
|
113
|
+
- [Smart Session](https://github.com/tomgrin10/paseo-smart-session) — Context compaction and usage insights.
|
|
114
|
+
- [Transcript Search](https://github.com/tomgrin10/paseo-transcript-search) — Search conversations across connected hosts.
|
|
115
|
+
- [Transfer](https://github.com/tomgrin10/paseo-transfer) — Copy or move workspaces between hosts.
|
|
116
|
+
- [Vitals](https://github.com/tomgrin10/paseo-vitals) — Host, process, agent and Docker monitoring.
|
|
117
|
+
- [Workspace Switcher](https://github.com/tomgrin10/paseo-switcher) — Arc-style Ctrl+Tab between recent workspaces.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
## Progress
|
|
4
|
+
|
|
5
|
+
Follow-up's questions (the ask, record and withdraw tools, the Stop, Work around and Keep building
|
|
6
|
+
question modes and their rules, the "big question" test, and the question dialog with lettered
|
|
7
|
+
options, a recommendation and Copy) and the activity log are adapted from
|
|
8
|
+
[Progress](https://github.com/stevecastaneda/paseo-plugins/tree/main/progress) (`@stevecastaneda/paseo-progress`
|
|
9
|
+
1.14.0) by Steve Castaneda.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
MIT License
|
|
13
|
+
|
|
14
|
+
Copyright (c) 2026 Steve Castaneda
|
|
15
|
+
|
|
16
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
17
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
18
|
+
in the Software without restriction, including without limitation the rights
|
|
19
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
20
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
21
|
+
furnished to do so, subject to the following conditions:
|
|
22
|
+
|
|
23
|
+
The above copyright notice and this permission notice shall be included in all
|
|
24
|
+
copies or substantial portions of the Software.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
28
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
29
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
30
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
31
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
32
|
+
SOFTWARE.
|
|
33
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
The following Lucide icons are derived from the Feather project:
|
|
20
|
+
|
|
21
|
+
airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
|
|
22
|
+
|
|
23
|
+
The MIT License (MIT) (for the icons listed above)
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2013-present Cole Bemis
|
|
26
|
+
|
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
in the Software without restriction, including without limitation the rights
|
|
30
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
furnished to do so, subject to the following conditions:
|
|
33
|
+
|
|
34
|
+
The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
copies or substantial portions of the Software.
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|
package/assets/icon.png
ADDED
|
Binary file
|
package/assets/icon.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256"><title>Follow-up</title><rect width="256" height="256" rx="52" fill="#1c2229"/><svg x="52" y="52" xmlns="http://www.w3.org/2000/svg" width="152" height="152" viewBox="0 0 24 24" fill="none" stroke="#f3f0e8" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-checks" aria-hidden="true"><path d="M13 5h8"></path><path d="M13 12h8"></path><path d="M13 19h8"></path><path d="m3 17 2 2 4-4"></path><path d="m3 7 2 2 4-4"></path></svg></svg>
|
|
@@ -25,7 +25,8 @@ export function withButtonVisibility(client: PluginClientContext) {
|
|
|
25
25
|
buttons.addComposerPill = input => gated("addComposerPill", input);
|
|
26
26
|
const unsubscribe = subscribeDisplay(values => {
|
|
27
27
|
enableHeader = values.enable_header_button;
|
|
28
|
-
|
|
28
|
+
// Unknown is not off: older saved documents predate the pill preference.
|
|
29
|
+
enableComposer = values.enable_composer_pill !== false;
|
|
29
30
|
for (const entry of entries) entry.reconcile();
|
|
30
31
|
});
|
|
31
32
|
return { client: buttons, dispose() { unsubscribe(); for (const entry of [...entries]) entry.remove(); } };
|
package/client/chat.tsx
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Platform, Pressable, Text, View } from "react-native";
|
|
3
|
+
import type { PluginClientContext, PluginTimelineItemProps } from "@getpaseo/plugin/client";
|
|
4
|
+
import { Icon, ScrollView } from "@getpaseo/plugin/client/ui";
|
|
5
|
+
import { answerCardSchema, batchCardSchema, followShellCall, followToolName, parseBatchPrompt, readText, toolRow, toolRowSchema, type AnswerCard, type BatchCard, type ToolRow } from "../shared/chat";
|
|
6
|
+
import { parseAnswerMessage } from "../shared/questions";
|
|
7
|
+
import { Reveal } from "./motion";
|
|
8
|
+
import { tint, withAlpha } from "./theme";
|
|
9
|
+
|
|
10
|
+
const mono = () => (Platform.OS === "ios" ? "Menlo" : "monospace");
|
|
11
|
+
const TOOL_KIND = "tool-row";
|
|
12
|
+
const BATCH_KIND = "batch-card";
|
|
13
|
+
const ANSWER_KIND = "answer-card";
|
|
14
|
+
const TOOL_ICON: Partial<Record<ToolRow["tool"], string>> = { ask_question: "MessageCircleQuestion", record_answer: "MessageCircleReply", withdraw_question: "MessageCircleOff", set_spec: "FileText" };
|
|
15
|
+
|
|
16
|
+
function record(value: unknown): value is Record<string, unknown> { return typeof value === "object" && value !== null && !Array.isArray(value); }
|
|
17
|
+
|
|
18
|
+
/** Expandable full text below a row, scrolling on its own so long output never moves the chat. */
|
|
19
|
+
function Expanded({ text, color, border }: { text: string; color: string; border: string }) {
|
|
20
|
+
return <View style={{ borderWidth: 1, borderTopWidth: 0, borderColor: border, borderBottomLeftRadius: 8, borderBottomRightRadius: 8, overflow: "hidden" }}>
|
|
21
|
+
<ScrollView style={[{ maxHeight: 300 }, Platform.OS === "web" ? ({ overscrollBehavior: "contain" } as object) : null]} contentContainerStyle={{ paddingHorizontal: 12, paddingVertical: 8 }}>
|
|
22
|
+
<Text selectable style={{ color, fontFamily: mono(), fontSize: 12, lineHeight: 18 }}>{text}</Text>
|
|
23
|
+
</ScrollView>
|
|
24
|
+
</View>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Follow-up's own tool calls as one quiet line, sized like Paseo's tool rows; press to read the result. */
|
|
28
|
+
function ToolRowView({ item, theme }: PluginTimelineItemProps<ToolRow>) {
|
|
29
|
+
const c = theme.colors;
|
|
30
|
+
const row = item.data;
|
|
31
|
+
const [expanded, setExpanded] = useState(false);
|
|
32
|
+
const [hovered, setHovered] = useState(false);
|
|
33
|
+
if (row.done) return <DoneCard summary={row.done.summary} theme={theme} />;
|
|
34
|
+
const color = row.tone === "danger" ? c.statusDanger : row.tone === "warning" ? c.statusWarning : row.tone === "success" ? c.statusSuccess : c.foregroundMuted;
|
|
35
|
+
const openable = row.more !== null;
|
|
36
|
+
const active = openable && (hovered || expanded);
|
|
37
|
+
const open = expanded && openable;
|
|
38
|
+
return <View style={{ marginHorizontal: -13 }}>
|
|
39
|
+
<Pressable disabled={!openable} onPress={() => setExpanded(v => !v)} onHoverIn={() => setHovered(true)} onHoverOut={() => setHovered(false)}
|
|
40
|
+
accessibilityRole={openable ? "button" : undefined} aria-expanded={openable ? expanded : undefined} aria-busy={row.state === "running"}
|
|
41
|
+
style={{ flexDirection: "row", alignItems: "center", borderRadius: 8, borderWidth: 1, borderColor: open ? c.border : "transparent", borderBottomLeftRadius: open ? 0 : 8, borderBottomRightRadius: open ? 0 : 8, backgroundColor: open ? c.surface1 : "transparent", paddingHorizontal: 8, paddingVertical: 4 }}>
|
|
42
|
+
<View style={{ width: 22, height: 22, marginRight: 4, alignItems: "center", justifyContent: "center" }}>
|
|
43
|
+
{active ? <View style={{ transform: [{ rotate: expanded ? "90deg" : "0deg" }] }}><Icon name="ChevronRight" size={14} color={c.foreground} /></View>
|
|
44
|
+
: <Icon name={row.state === "failed" ? "TriangleAlert" : TOOL_ICON[row.tool] ?? (row.tone === "warning" ? "CircleAlert" : "GitPullRequest")} size={12} color={row.tone === "muted" ? c.foregroundMuted : color} />}
|
|
45
|
+
</View>
|
|
46
|
+
<Text numberOfLines={1} style={{ flexShrink: 1, fontSize: 14, color: row.tone === "muted" ? (active || row.state === "running" ? c.foreground : c.foregroundMuted) : color, opacity: row.state === "running" ? 0.72 : 1 }}>{row.line}</Text>
|
|
47
|
+
</Pressable>
|
|
48
|
+
{open && <Expanded text={row.more!} color={row.state === "failed" ? c.statusDanger : c.foreground} border={c.border} />}
|
|
49
|
+
</View>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The agent declared the workspace done: the one row in the chat that is allowed to be a little proud. */
|
|
53
|
+
function DoneCard({ summary, theme }: { summary: string; theme: PluginTimelineItemProps["theme"] }) {
|
|
54
|
+
const c = theme.colors;
|
|
55
|
+
return <Reveal>
|
|
56
|
+
<View accessibilityRole="summary" style={{ flexDirection: "row", gap: 12, padding: 12, borderRadius: 10, backgroundColor: tint(c.statusSuccess, c), borderWidth: 1, borderColor: withAlpha(c.statusSuccess, 0.35), marginVertical: 4 }}>
|
|
57
|
+
<View style={{ width: 28, height: 28, borderRadius: 14, alignItems: "center", justifyContent: "center", backgroundColor: c.statusSuccess }}><Icon name="Check" size={16} color={c.surface0} /></View>
|
|
58
|
+
<View style={{ flex: 1, gap: 3 }}>
|
|
59
|
+
<Text style={{ color: c.foreground, fontSize: 14, lineHeight: 19, fontWeight: "600" }}>Workspace done</Text>
|
|
60
|
+
{!!summary && <Text selectable style={{ color: c.foreground, fontSize: 13, lineHeight: 19 }}>{summary}</Text>}
|
|
61
|
+
<Text style={{ color: c.foregroundMuted, fontSize: 12, lineHeight: 17 }}>Follow-up checked every PR and task. Archive the workspace when you're ready.</Text>
|
|
62
|
+
</View>
|
|
63
|
+
</View>
|
|
64
|
+
</Reveal>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A repair batch Follow-up sent, as a card instead of a raw prompt bubble. */
|
|
68
|
+
function BatchCardView({ item, theme }: PluginTimelineItemProps<BatchCard>) {
|
|
69
|
+
const c = theme.colors;
|
|
70
|
+
const card = item.data;
|
|
71
|
+
const [expanded, setExpanded] = useState(false);
|
|
72
|
+
const recovery = card.kind === "recovery";
|
|
73
|
+
return <View style={{ alignSelf: "flex-end", width: "100%", maxWidth: 560, marginVertical: 4 }}>
|
|
74
|
+
<View style={{ borderRadius: 10, borderWidth: 1, borderColor: c.border, backgroundColor: c.surface1, overflow: "hidden" }}>
|
|
75
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, paddingHorizontal: 12, paddingTop: 10, paddingBottom: recovery ? 10 : 6 }}>
|
|
76
|
+
<Icon name={recovery ? "History" : "Wrench"} size={14} color={c.foregroundMuted} />
|
|
77
|
+
<Text style={{ flex: 1, color: c.foreground, fontSize: 13, lineHeight: 18, fontWeight: "600" }}>{recovery ? "Follow-up recovery brief" : `Follow-up sent ${card.tasks.length} repair${card.tasks.length === 1 ? "" : "s"}`}</Text>
|
|
78
|
+
<Pressable accessibilityRole="button" aria-expanded={expanded} accessibilityLabel={expanded ? "Hide the full message" : "Show the full message"} onPress={() => setExpanded(v => !v)} hitSlop={6}
|
|
79
|
+
style={state => ({ paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, backgroundColor: state.pressed ? c.surface2 : "transparent" })}>
|
|
80
|
+
<Text style={{ color: c.foregroundMuted, fontSize: 12 }}>{expanded ? "Hide" : "Full message"}</Text>
|
|
81
|
+
</Pressable>
|
|
82
|
+
</View>
|
|
83
|
+
{!recovery && <View style={{ paddingHorizontal: 12, paddingBottom: 10, gap: 3 }}>
|
|
84
|
+
{card.tasks.map((task, i) => <View key={i} style={{ flexDirection: "row", gap: 6 }}>
|
|
85
|
+
<Text style={{ width: 18, color: c.foregroundMuted, fontSize: 13, lineHeight: 19, fontVariant: ["tabular-nums"] }}>{i + 1}.</Text>
|
|
86
|
+
<Text selectable style={{ flex: 1, color: c.foreground, fontSize: 13, lineHeight: 19 }}>{task}</Text>
|
|
87
|
+
</View>)}
|
|
88
|
+
</View>}
|
|
89
|
+
{expanded && <View style={{ borderTopWidth: 1, borderTopColor: c.border }}>
|
|
90
|
+
<ScrollView style={[{ maxHeight: 300 }, Platform.OS === "web" ? ({ overscrollBehavior: "contain" } as object) : null]} contentContainerStyle={{ padding: 12 }}>
|
|
91
|
+
<Text selectable style={{ color: c.foregroundMuted, fontFamily: mono(), fontSize: 12, lineHeight: 18 }}>{card.text}</Text>
|
|
92
|
+
</ScrollView>
|
|
93
|
+
</View>}
|
|
94
|
+
</View>
|
|
95
|
+
</View>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The developer's answer, sent from Follow-up: the question, the choice and their note. */
|
|
99
|
+
function AnswerCardView({ item, theme }: PluginTimelineItemProps<AnswerCard>) {
|
|
100
|
+
const c = theme.colors;
|
|
101
|
+
const card = item.data;
|
|
102
|
+
const [expanded, setExpanded] = useState(false);
|
|
103
|
+
return <View style={{ alignSelf: "flex-end", width: "100%", maxWidth: 560, marginVertical: 4 }}>
|
|
104
|
+
<View style={{ borderRadius: 10, borderWidth: 1, borderColor: c.border, backgroundColor: c.surface1, overflow: "hidden" }}>
|
|
105
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, paddingHorizontal: 12, paddingTop: 10, paddingBottom: 6 }}>
|
|
106
|
+
<Icon name="MessageCircleReply" size={14} color={c.statusSuccess} />
|
|
107
|
+
<Text numberOfLines={1} style={{ flex: 1, color: c.foreground, fontSize: 13, lineHeight: 18, fontWeight: "600" }}>Your answer to {card.id} · {card.title}</Text>
|
|
108
|
+
<Pressable accessibilityRole="button" aria-expanded={expanded} accessibilityLabel={expanded ? "Hide the full message" : "Show the full message"} onPress={() => setExpanded(v => !v)} hitSlop={6}
|
|
109
|
+
style={state => ({ paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, backgroundColor: state.pressed ? c.surface2 : "transparent" })}>
|
|
110
|
+
<Text style={{ color: c.foregroundMuted, fontSize: 12 }}>{expanded ? "Hide" : "Full message"}</Text>
|
|
111
|
+
</Pressable>
|
|
112
|
+
</View>
|
|
113
|
+
<View style={{ paddingHorizontal: 12, paddingBottom: 10, gap: 3 }}>
|
|
114
|
+
<Text selectable style={{ color: c.foreground, fontSize: 13, lineHeight: 19 }}><Text style={{ fontWeight: "600", fontVariant: ["tabular-nums"] }}>{card.choice}</Text>{card.label ? ` · ${card.label}` : ""}</Text>
|
|
115
|
+
{!!card.words && <Text selectable style={{ color: c.foregroundMuted, fontSize: 13, lineHeight: 19 }}>{card.words}</Text>}
|
|
116
|
+
</View>
|
|
117
|
+
{expanded && <View style={{ borderTopWidth: 1, borderTopColor: c.border }}>
|
|
118
|
+
<ScrollView style={[{ maxHeight: 300 }, Platform.OS === "web" ? ({ overscrollBehavior: "contain" } as object) : null]} contentContainerStyle={{ padding: 12 }}>
|
|
119
|
+
<Text selectable style={{ color: c.foregroundMuted, fontFamily: mono(), fontSize: 12, lineHeight: 18 }}>{card.text}</Text>
|
|
120
|
+
</ScrollView>
|
|
121
|
+
</View>}
|
|
122
|
+
</View>
|
|
123
|
+
</View>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function contributeChat(client: PluginClientContext) {
|
|
127
|
+
const removers = [
|
|
128
|
+
client.addTimelineTransformer({
|
|
129
|
+
id: "follow-tool-calls", query: { itemType: "tool_call" },
|
|
130
|
+
transform({ item }) {
|
|
131
|
+
const detail: unknown = item.detail;
|
|
132
|
+
let tool = followToolName(item.name);
|
|
133
|
+
let input: Record<string, unknown> = {};
|
|
134
|
+
let output: string | undefined;
|
|
135
|
+
if (tool) {
|
|
136
|
+
if (record(detail) && record(detail.input)) input = detail.input;
|
|
137
|
+
output = record(detail) && "output" in detail ? readText(detail.output) : undefined;
|
|
138
|
+
} else if (record(detail) && detail.type === "shell" && typeof detail.command === "string") {
|
|
139
|
+
const call = followShellCall(detail.command);
|
|
140
|
+
if (!call) return undefined;
|
|
141
|
+
tool = call.tool; input = call.input; output = typeof detail.output === "string" ? detail.output : undefined;
|
|
142
|
+
} else return undefined;
|
|
143
|
+
const error = item.status === "failed" ? readText(item.error) ?? "" : undefined;
|
|
144
|
+
if (item.status === "completed" && record(detail) && record(detail.output) && detail.output.isError === true)
|
|
145
|
+
return { items: [{ type: "plugin" as const, kind: TOOL_KIND, version: 1, data: toolRow(tool, "failed", input, undefined, output) }] };
|
|
146
|
+
return { items: [{ type: "plugin" as const, kind: TOOL_KIND, version: 1, data: toolRow(tool, item.status, input, output, error) }] };
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
client.addTimelineTransformer({
|
|
150
|
+
id: "follow-batch-prompts", query: { itemType: "user_message" },
|
|
151
|
+
transform({ item }) {
|
|
152
|
+
const answer = parseAnswerMessage(item.text);
|
|
153
|
+
if (answer) return { items: [{ type: "plugin" as const, kind: ANSWER_KIND, version: 1, data: { ...answer, text: item.text } }] };
|
|
154
|
+
const card = parseBatchPrompt(item.text);
|
|
155
|
+
return card ? { items: [{ type: "plugin" as const, kind: BATCH_KIND, version: 1, data: card }] } : undefined;
|
|
156
|
+
},
|
|
157
|
+
}),
|
|
158
|
+
client.addTimelineRenderer({ kind: TOOL_KIND, version: 1, schema: toolRowSchema, Component: ToolRowView }),
|
|
159
|
+
client.addTimelineRenderer({ kind: BATCH_KIND, version: 1, schema: batchCardSchema, Component: BatchCardView }),
|
|
160
|
+
client.addTimelineRenderer({ kind: ANSWER_KIND, version: 1, schema: answerCardSchema, Component: AnswerCardView }),
|
|
161
|
+
];
|
|
162
|
+
return () => { for (const remove of removers) void remove(); };
|
|
163
|
+
}
|
package/client/display.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PluginClientContext,
|
|
1
|
+
import type { PluginClientContext, PluginSidebarItemContribution, PluginSurfaceProps } from "@getpaseo/plugin/client";
|
|
2
2
|
import { useSettings } from "@getpaseo/plugin/client";
|
|
3
3
|
import { SettingsAction, SettingsCard, SettingsSection, SettingsSwitch } from "@getpaseo/plugin/client/ui";
|
|
4
4
|
import { useEffect } from "react";
|
|
@@ -30,19 +30,14 @@ function DisplaySettings({ theme }: PluginSurfaceProps) {
|
|
|
30
30
|
</View>
|
|
31
31
|
);
|
|
32
32
|
|
|
33
|
+
const save = (patch: Partial<Display>) => void settings.save({ ...settings.values, ...patch }, settings.revision);
|
|
33
34
|
return (
|
|
34
35
|
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 16 }}>
|
|
35
|
-
<SettingsSection title="
|
|
36
|
+
<SettingsSection title="Where Follow-up appears" info="Every entry point opens the same panel. Command-K (Ctrl-K on Windows and Linux) works whatever you hide here.">
|
|
36
37
|
<SettingsCard>
|
|
37
|
-
<SettingsSwitch
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
value={settings.values.showSidebar}
|
|
41
|
-
disabled={settings.saving}
|
|
42
|
-
onValueChange={(showSidebar) => void settings.save({ ...settings.values, showSidebar }, settings.revision)}
|
|
43
|
-
/>
|
|
44
|
-
|
|
45
|
-
<SettingsSwitch label="Show workspace header button" hint="Command-K and the sidebar remain available when this button is hidden." value={settings.values.enable_header_button} disabled={settings.saving} onValueChange={(enable_header_button) => void settings.save({ ...settings.values, enable_header_button }, settings.revision)} />
|
|
38
|
+
<SettingsSwitch label="Sidebar row" hint="Opens all tracked workspaces, with a count when some need you." value={settings.values.showSidebar} disabled={settings.saving} onValueChange={(showSidebar) => save({ showSidebar })} />
|
|
39
|
+
<SettingsSwitch label="Workspace header button" hint="An icon in each workspace header. A dot appears when that workspace needs you." value={settings.values.enable_header_button} disabled={settings.saving} onValueChange={(enable_header_button) => save({ enable_header_button })} />
|
|
40
|
+
<SettingsSwitch label="Composer pill" hint="Shows above the message box only while the workspace needs you." value={settings.values.enable_composer_pill} disabled={settings.saving} onValueChange={(enable_composer_pill) => save({ enable_composer_pill })} />
|
|
46
41
|
</SettingsCard>
|
|
47
42
|
{settings.saveError && <Text accessibilityRole="alert" style={{ color: theme.colors.statusDanger }}>{settings.saveError}</Text>}
|
|
48
43
|
</SettingsSection>
|
|
@@ -51,22 +46,22 @@ function DisplaySettings({ theme }: PluginSurfaceProps) {
|
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
/** Settings notifications reach mounted screens; poll for changes made on other clients. */
|
|
54
|
-
export function contributeDisplay(client: PluginClientContext, item:
|
|
55
|
-
const sidebar = sidebarVisibility(() => client.
|
|
49
|
+
export function contributeDisplay(client: PluginClientContext, item: PluginSidebarItemContribution) {
|
|
50
|
+
const sidebar = sidebarVisibility(() => client.addSidebarHeaderItem(item));
|
|
56
51
|
// Paseo 0.10.1 memoizes sidebar route resolution when an installation mounts.
|
|
57
52
|
// Register synchronously, then reconcile the saved preference, so that an
|
|
58
53
|
// already-open sidebar route can resolve its surface during that first render.
|
|
59
54
|
sidebar.set(true);
|
|
60
55
|
const Settings = DisplaySettings;
|
|
61
|
-
const removeSurface = client.
|
|
56
|
+
const removeSurface = client.addScreen({ id: "display-settings", title: "Follow-up display", Component: Settings });
|
|
62
57
|
const removeSettings = client.addSettingsScreen({ id: "display", title: "Display", icon: "PanelLeft", Component: Settings });
|
|
63
58
|
const removeCommand = client.addCommandCenterItem({
|
|
64
59
|
id: "display-settings",
|
|
65
|
-
title:
|
|
60
|
+
title: "Follow-up: display settings",
|
|
66
61
|
icon: "PanelLeft",
|
|
67
|
-
keywords: [
|
|
62
|
+
keywords: ["Follow-up", "sidebar", "pill", "header", "show", "hide", "display", "settings"],
|
|
68
63
|
context: "global",
|
|
69
|
-
onSelect({
|
|
64
|
+
onSelect({ openScreen }) { openScreen({ screenId: "display-settings" }); },
|
|
70
65
|
});
|
|
71
66
|
let disposed = false;
|
|
72
67
|
let pending = false;
|