conciv

Widget UI

Render into the widget: slots, hooks, and the client API.

Beyond tool cards, an extension can render its own UI inside the widget: a composer button, a header control, a full panel. Attach a Component and pick where it shows with useSlot.

Extension components are Solid JSX, even when your app is React. The plugin compiles conciv/extensions/* as a Solid zone, so Show, signals, and stores all work as usual.

Slots

The widget mounts your Component once per slot and tells you where it currently is. Render nothing for slots you do not care about.

conciv/extensions/deploy.tsx
import {Show} from 'solid-js'
import {defineExtension} from '@conciv/extension'

const deploy = defineExtension({name: 'deploy', tools: [deployRun]})

function DeployButton() {
  const slot = deploy.useSlot()
  const context = deploy.useContext()
  return (
    <Show when={slot() === 'composer'}>
      <button type="button" onClick={() => context.insert('deploy this branch to staging')}>
        Deploy
      </button>
    </Show>
  )
}

export default Object.assign(deploy, {Component: DeployButton})

Slots: header, footer, composer, empty (the blank-thread state), status, and widget (a free-floating layer).

useContext

useContext() returns the host context: everything the widget gives your component. Pass a selector to subscribe to one slice.

Composer actions, always available:

Prop

Type

Plus client (the session API client), requestMeta() (session id and model for your own fetches), grab (the element-grab API), and currentSlot.

.client(factory)

Extension-specific client state lives in a .client(...) factory. Its return value is merged into what useContext() returns, fully typed:

const deploy = defineExtension({name: 'deploy', tools: [deployRun]}).client(() => {
  const [lastUrl, setLastUrl] = createSignal<string | null>(null)
  return {value: {lastUrl, setLastUrl}}
})

Return {value, dispose}; dispose runs when the widget unmounts.

The client API

useClientApi() (also exported standalone) is the widget-host surface:

Prop

Type

Mounting outside the widget

The widget host does all of the above for you. For a custom host (or a test harness) the same machinery is exported from @conciv/extension/client:

import {mountExtension, MountedExtension} from '@conciv/extension/client'

const dispose = mountExtension(extension, {clientApi, hostContext, slot: 'widget', root})

This is exactly what the testkit uses, so tested extensions run through their production mount path.

On this page