Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fbf210b6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mynameistito/opencode-force-input": patch
---

Mount footer UI through OpenTUI's renderer and register force-submit bindings once during setup
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,20 @@ The documentation site source lives in [`apps/web/docs/`](apps/web/docs/). Start

## Local Development

Build the workspace and load the source entrypoints from `~/.config/opencode/cli.json`:
Build the packages you want to test, then add their package directories to your TUI config (`~/.config/opencode/cli.json` on this machine). Preserve the rest of your config and use absolute paths for your checkout:

```jsonc
{
"$schema": "https://opencode.ai/v2/cli.json",
"plugins": [
"/path/to/opencode-plugins/packages/opencode-force-input/src/index.ts",
"/path/to/opencode-plugins/packages/opencode-usage-limits/src/index.ts",
"/path/to/opencode-plugins/packages/opencode-force-input",
"/path/to/opencode-plugins/packages/opencode-usage-limits",
],
}
```

Restart OpenCode after editing the config. In a session, confirm the idle `⏎ send` hint appears, start a prompt and confirm it changes to the `steer` hint, then press `Ctrl+Enter` to verify the active run is interrupted and the prompt is submitted.

## Development

```powershell
Expand Down
10 changes: 3 additions & 7 deletions packages/opencode-force-input/__tests__/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe("force submit", () => {
]);
});

it("mounts a layer for each prompt footer render and dispatches force-submit", async () => {
it("registers one layer and dispatches force-submit from the prompt footer", async () => {
const claims: SlotClaim[] = [];
const layers: (() => KeymapLayer)[] = [];
const dispatched: string[] = [];
Expand Down Expand Up @@ -92,18 +92,14 @@ describe("force submit", () => {
}

const renderSetup = await testRender(
() => {
claim.render({ mode: "normal", showDetails: false });

return claim.render({ mode: "normal", showDetails: false });
},
() => claim.render({ mode: "normal", showDetails: false }),
{ height: 3, width: 70 }
);

try {
await renderSetup.flush();

expect(layers).toHaveLength(2);
expect(layers).toHaveLength(1);
const [layer] = layers;

if (!layer) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* @jsxImportSource @opentui/solid */
import { Plugin } from "@opencode/plugin/tui";
import type { Context } from "@opencode/plugin/tui/context";
import { createComponent } from "@opentui/solid";
import type { JSX } from "@opentui/solid";

import { ForceHint } from "./force-hint.tsx";
Expand Down Expand Up @@ -87,32 +87,34 @@ interface PromptFooterContributionProps {
* same `prompt.footer.status` claim, so the binding and its affordance follow
* TUI context changes together.
*/
const promptFooterContribution = (
const PromptFooterContribution = (
props: PromptFooterContributionProps
): JSX.Element => {
registerForceSubmitLayer(props.context.keymap);

return createComponent(ForceHint, {
data: props.context.data,
enabled: hintEnabled(props.context.options),
mode: props.slot.mode,
sessionID: props.slot.sessionID,
theme: props.context.theme,
});
};
): JSX.Element => (
<ForceHint
data={props.context.data}
enabled={hintEnabled(props.context.options)}
mode={props.slot.mode}
sessionID={props.slot.sessionID}
theme={props.context.theme}
/>
);

/**
* Initializes the OpenCode v2 TUI plugin and registers the composer hint.
*
* @param context - Plugin context surfaces required by the setup lifecycle.
* @returns A disposer that unregisters the UI slot when the plugin is stopped.
*/
export const setup = (context: ForceSubmitContext): (() => void) =>
context.ui.slot({
export const setup = (context: ForceSubmitContext): (() => void) => {
registerForceSubmitLayer(context.keymap);

return context.ui.slot({
append: "prompt.footer.status",
render: (slot) =>
createComponent(promptFooterContribution, { context, slot }),
render: (slot) => (
<PromptFooterContribution context={context} slot={slot} />
),
});
};

/** OpenCode v2 plugin entrypoint registering the force-input feature. */
export default Plugin.define({
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode-force-input/tsdown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default defineConfig({
dts: {
sourcemap: true,
},
entry: ["src/index.ts"],
entry: ["src/index.tsx"],
format: ["esm"],
outDir: "dist",
sourcemap: true,
Expand Down
Loading