diff --git a/.changeset/fbf210b6.md b/.changeset/fbf210b6.md
new file mode 100644
index 0000000..893ede2
--- /dev/null
+++ b/.changeset/fbf210b6.md
@@ -0,0 +1,5 @@
+---
+"@mynameistito/opencode-force-input": patch
+---
+
+Mount footer UI through OpenTUI's renderer and register force-submit bindings once during setup
diff --git a/README.md b/README.md
index 564586d..7025c23 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/packages/opencode-force-input/__tests__/plugin.test.ts b/packages/opencode-force-input/__tests__/plugin.test.ts
index c2be82d..18d7f54 100644
--- a/packages/opencode-force-input/__tests__/plugin.test.ts
+++ b/packages/opencode-force-input/__tests__/plugin.test.ts
@@ -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[] = [];
@@ -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) {
diff --git a/packages/opencode-force-input/src/index.ts b/packages/opencode-force-input/src/index.tsx
similarity index 87%
rename from packages/opencode-force-input/src/index.ts
rename to packages/opencode-force-input/src/index.tsx
index befeaac..64bddb5 100644
--- a/packages/opencode-force-input/src/index.ts
+++ b/packages/opencode-force-input/src/index.tsx
@@ -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";
@@ -87,19 +87,17 @@ 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 => (
+
+);
/**
* Initializes the OpenCode v2 TUI plugin and registers the composer hint.
@@ -107,12 +105,16 @@ const promptFooterContribution = (
* @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) => (
+
+ ),
});
+};
/** OpenCode v2 plugin entrypoint registering the force-input feature. */
export default Plugin.define({
diff --git a/packages/opencode-force-input/tsdown.config.ts b/packages/opencode-force-input/tsdown.config.ts
index c7424a6..79353de 100644
--- a/packages/opencode-force-input/tsdown.config.ts
+++ b/packages/opencode-force-input/tsdown.config.ts
@@ -17,7 +17,7 @@ export default defineConfig({
dts: {
sourcemap: true,
},
- entry: ["src/index.ts"],
+ entry: ["src/index.tsx"],
format: ["esm"],
outDir: "dist",
sourcemap: true,