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
39 changes: 39 additions & 0 deletions .sync/log/7bdcb1381c7cb55c7fd602545b3ddc4461b19c43.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Port: feat(Prompt): add `claude` action (#6693)

**Upstream:** `7bdcb1381c7cb55c7fd602545b3ddc4461b19c43` (nuxt/ui)
**Decision:** port

## Upstream change
`prose/Prompt.vue` gains a `claude` action alongside `copy`/`cursor`/`windsurf`:
the `copy` button is now **always** shown (default `actions` → `[]`, and a new
`actions` computed forces `copy` in via `[...new Set(['copy', ...props.actions])]`);
adds `openInClaude()` (`claude://code/new?q=<prompt>`); the `openIn*` helpers are
condensed to one-liners with `encodeURIComponent`; template `v-if`s switch from
`props.actions` to the `actions` computed and a Claude `UButton` is added. Docs
`prompt.md` updated (copy always displayed; example uses `cursor`+`claude`).

## b24ui port — adapted (icon divergence)
b24ui's `prose/Prompt.vue` matched upstream (with b24ui naming: `b24ui`,
`B24Button`, `appConfig.b24ui`, `icons` from `dictionary/icons`). Applied the
type (`+'claude'` + jsdoc), the `actions: () => []` default, the `actions`
computed, the `openIn*` refactor + `openInClaude`, and the template
`actions.includes(...)` switch + the Claude `B24Button`.

### Deviation — inline `ClaudeIcon`
Upstream uses `icon="i-simple-icons-claude"` (iconify). b24ui has no
`@nuxt/icon`/iconify and no Claude brand icon in `@bitrix24/b24icons-vue`, so —
mirroring the existing hand-rolled `CursorIcon`/`WindsurfIcon` inline-SVG
`IconComponent`s in `dictionary/icons.ts` — added a `ClaudeIcon` inline SVG using
the **simple-icons `claude`** path (fetched verbatim from the simple-icons
package), registered in the icons dictionary. The Claude button uses
`:icon="icons.ClaudeIcon"` + `color="air-secondary-accent-2"` (matching
Cursor/Windsurf).

## Tests
No upstream Prompt spec change. 14 snapshots updated (`ProsePrompt` × nuxt/vue):
benign — existing cases gain the Claude button's `<!--v-if-->` placeholder (it's
inactive unless `claude` is requested), and the copy button now always renders
via the `actions` computed. Suite 5289 passed.

## Verify (CI=true)
`dev:prepare` · `lint` · `typecheck` · `test` · `build` — all green.
10 changes: 8 additions & 2 deletions .sync/nuxt-ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"upstream": "nuxt/ui",
"branch": "v4",
"sync_enabled": false,
"cursor": "d0581cf88fa13008871484e5f88fdb0f9f744ff7",
"cursor": "7bdcb1381c7cb55c7fd602545b3ddc4461b19c43",
"_cursor_note": "cursor = last upstream commit ported into b24ui (oldest-first, manual cadence). sync_enabled stays false until Phase 2 (porter workflow #67 + CLAUDE_CODE_OAUTH_TOKEN) is wired and trusted. `processed` is maintained per port from now on (backfilled #68-#72 on 2026-06-09).",
"stats": {
"queue_depth": 0,
Expand Down Expand Up @@ -929,10 +929,16 @@
"summary": "feat(unplugin): pre-bundle used icons into the Vue/Vite build (#6635) — NO-OP: Vue/Vite sibling of 8d46034 (@nuxt/icon client bundle). New IconsPlugin (src/plugins/icons.ts) emitting virtual:nuxt-ui-icons + runtime/vue/plugins/icons.ts (@iconify/vue addIcon) + types/icons.d.ts + utils/icons.ts; unplugin.ts +icon.clientBundle option (from @nuxt/icon ModuleOptions) + registers IconsPlugin; plugins.ts unshifts icons plugin. All @nuxt/icon+@iconify+@iconify-json. b24ui uses @bitrix24/b24icons-vue components: no @nuxt/icon/@iconify deps, none of the icon files, and b24ui's unplugin.ts has no @nuxt/icon import/icon option/IconsPlugin and plugins.ts registers no icon plugin. Nothing to wire. Same basis as #69/#219/#8d46034. Bookkeeping only"
},
"d0581cf88fa13008871484e5f88fdb0f9f744ff7": {
"pr": 254,
"b24ui_sha": "eb0990c5",
"decision": "port",
"summary": "test(ChatPrompt): avoid using fake timers before suspended (#6688) — test-only: move vi.useFakeTimers() from before mountSuspended() to after it in the 're-enables submit after compositionend cooldown' case so fake timers don't interfere with the async suspension lifecycle. Direct 1:1 (b24ui matched). No src change, no snapshot churn. Tests 5289"
},
"7bdcb1381c7cb55c7fd602545b3ddc4461b19c43": {
"pr": null,
"b24ui_sha": "pending-merge",
"decision": "port",
"summary": "test(ChatPrompt): avoid using fake timers before suspended (#6688) — test-only: move vi.useFakeTimers() from before mountSuspended() to after it in the 're-enables submit after compositionend cooldown' case so fake timers don't interfere with the async suspension lifecycle. Direct 1:1 (b24ui matched). No src change, no snapshot churn. Tests 5289"
"summary": "feat(Prompt): add claude action (#6693) — prose/Prompt.vue: +claude action; copy now always shown (default actions->[], +computed actions=[...new Set(['copy',...props.actions])]); +openInClaude (claude://code/new?q=), openIn* condensed to encodeURIComponent one-liners; template v-if props.actions->actions computed + Claude B24Button. Docs prompt.md updated (copy always; example cursor+claude). Adapted (b24ui naming). DEVIATION: upstream icon=i-simple-icons-claude (iconify); b24ui has no @nuxt/icon/Claude b24icon, so added inline ClaudeIcon SVG to dictionary/icons.ts (simple-icons claude path, verbatim) mirroring existing CursorIcon/WindsurfIcon; button color air-secondary-accent-2. 14 ProsePrompt snapshots (benign: claude v-if placeholder + copy always via computed). Tests 5289"
}
}
}
5 changes: 2 additions & 3 deletions docs/content/docs/4.typography/prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ slots:

### Actions

Use the `actions` prop to control which buttons are displayed. Defaults to `["copy"]`. Available actions are `copy`, `cursor` and `windsurf`.
Use the `actions` prop to display additional buttons. The `copy` button is always displayed, available actions are `cursor`, `windsurf` and `claude`.

::component-code{slug="prompt" prose}
---
Expand All @@ -49,9 +49,8 @@ hide:
props:
description: Add a color mode toggle.
actions:
- copy
- cursor
- windsurf
- claude
class: 'w-full my-0'
slots:
default: |
Expand Down
35 changes: 23 additions & 12 deletions src/runtime/components/prose/Prompt.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ export interface ProsePromptProps {
*/
iconName?: string
/**
* The `copy` action is always displayed, list any additional actions to show alongside it.
* @defaultValue ['copy']
*/
actions?: ('copy' | 'cursor' | 'windsurf')[]
actions?: ('copy' | 'cursor' | 'windsurf' | 'claude')[]
class?: any
b24ui?: ProsePrompt['slots']
}
Expand All @@ -48,7 +49,7 @@ import B24Button from '../Button.vue'
defineOptions({ inheritAttrs: false })

const _props = withDefaults(defineProps<ProsePromptProps>(), {
actions: () => ['copy']
actions: () => []
})
defineSlots<ProsePromptSlots>()

Expand All @@ -63,6 +64,9 @@ const bodyRef = useTemplateRef<HTMLElement>('bodyRef')
// eslint-disable-next-line vue/no-dupe-keys
const b24ui = computed(() => tv({ extend: theme, ...(appConfig.b24ui?.prose?.prompt || {}) })())

// eslint-disable-next-line vue/no-dupe-keys
const actions = computed(() => [...new Set(['copy', ...props.actions])])

const iconFromIconName = computed(() => resolveIcon(props.iconName))

function getPromptText() {
Expand All @@ -74,17 +78,15 @@ function copyPrompt() {
}

function openInCursor() {
const url = new URL('cursor://anysphere.cursor-deeplink/prompt')
url.searchParams.set('text', getPromptText())

window.open(url.toString(), '_self')
window.open(`cursor://anysphere.cursor-deeplink/prompt?text=${encodeURIComponent(getPromptText())}`, '_self')
}

function openInWindsurf() {
const url = new URL('windsurf://cascade/newChat')
url.searchParams.set('prompt', getPromptText())
window.open(`windsurf://cascade/newChat?prompt=${encodeURIComponent(getPromptText())}`, '_self')
}

window.open(url.toString(), '_self')
function openInClaude() {
window.open(`claude://code/new?q=${encodeURIComponent(getPromptText())}`, '_self')
}
</script>

Expand Down Expand Up @@ -114,7 +116,7 @@ function openInWindsurf() {

<div data-slot="actions" :class="b24ui.actions({ class: props.b24ui?.actions })">
<B24Button
v-if="props.actions.includes('copy')"
v-if="actions.includes('copy')"
:icon="copied ? icons.copyCheck : icons.copy"
color="air-primary-copilot"
size="sm"
Expand All @@ -123,7 +125,7 @@ function openInWindsurf() {
/>

<B24Button
v-if="props.actions.includes('cursor')"
v-if="actions.includes('cursor')"
:icon="icons.CursorIcon"
color="air-secondary-accent-2"
size="sm"
Expand All @@ -132,13 +134,22 @@ function openInWindsurf() {
/>

<B24Button
v-if="props.actions.includes('windsurf')"
v-if="actions.includes('windsurf')"
:icon="icons.WindsurfIcon"
color="air-secondary-accent-2"
size="sm"
:label="t('prose.prompt.openIn', { name: 'Windsurf' })"
@click="openInWindsurf"
/>

<B24Button
v-if="actions.includes('claude')"
:icon="icons.ClaudeIcon"
color="air-secondary-accent-2"
size="sm"
:label="t('prose.prompt.openIn', { name: 'Claude' })"
@click="openInClaude"
/>
</div>
</div>
</template>
15 changes: 14 additions & 1 deletion src/runtime/dictionary/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ const WindsurfIcon: IconComponent = () => h('svg', {
})
])

const ClaudeIcon: IconComponent = () => h('svg', {
'xmlns': 'http://www.w3.org/2000/svg',
'viewBox': '0 0 24 24',
'aria-hidden': 'true',
'data-slot': 'icon'
}, [
h('path', {
fill: 'currentColor',
d: 'm4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z'
})
])

export default {
arrowLeft: ArrowToTheLeftIcon,
arrowRight: ArrowToTheRightIcon,
Expand Down Expand Up @@ -139,5 +151,6 @@ export default {
EncloseTextInCodeTagIcon, // source / "View code" CTA
PlayLIcon, // "Try it" / playground / REPL CTA
CursorIcon, // Cursor IDE — open prompt in app
WindsurfIcon // Windsurf IDE — open prompt in app
WindsurfIcon, // Windsurf IDE — open prompt in app
ClaudeIcon // Claude Code — open prompt in app
}
10 changes: 9 additions & 1 deletion test/components/__snapshots__/ProsePrompt-vue.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ exports[`ProsePrompt > renders with actions correctly 1`] = `
</svg><span data-slot="label" class="h-(--ui-btn-height) max-w-full inline-flex flex-row items-center tracking-(--ui-btn-letter-spacing) overflow-visible text-clip"><span data-slot="labelInner" class="truncate inline-block max-w-full mt-(--ui-btn-title-compensation)">Open in Windsurf</span></span>
<!--v-if-->
</div>
</button></div>
</button>
<!--v-if-->
</div>
</div>"
`;

Expand All @@ -51,6 +53,7 @@ exports[`ProsePrompt > renders with b24ui correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
Expand All @@ -73,6 +76,7 @@ exports[`ProsePrompt > renders with class correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
Expand All @@ -95,6 +99,7 @@ exports[`ProsePrompt > renders with default slot correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
Expand All @@ -117,6 +122,7 @@ exports[`ProsePrompt > renders with description correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
Expand All @@ -140,6 +146,7 @@ exports[`ProsePrompt > renders with icon correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
Expand All @@ -164,6 +171,7 @@ exports[`ProsePrompt > renders with iconName correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
10 changes: 9 additions & 1 deletion test/components/__snapshots__/ProsePrompt.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ exports[`ProsePrompt > renders with actions correctly 1`] = `
</svg><span data-slot="label" class="h-(--ui-btn-height) max-w-full inline-flex flex-row items-center tracking-(--ui-btn-letter-spacing) overflow-visible text-clip"><span data-slot="labelInner" class="truncate inline-block max-w-full mt-(--ui-btn-title-compensation)">Open in Windsurf</span></span>
<!--v-if-->
</div>
</button></div>
</button>
<!--v-if-->
</div>
</div>"
`;

Expand All @@ -51,6 +53,7 @@ exports[`ProsePrompt > renders with b24ui correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
Expand All @@ -73,6 +76,7 @@ exports[`ProsePrompt > renders with class correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
Expand All @@ -95,6 +99,7 @@ exports[`ProsePrompt > renders with default slot correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
Expand All @@ -117,6 +122,7 @@ exports[`ProsePrompt > renders with description correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
Expand All @@ -140,6 +146,7 @@ exports[`ProsePrompt > renders with icon correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;
Expand All @@ -164,6 +171,7 @@ exports[`ProsePrompt > renders with iconName correctly 1`] = `
</button>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
</div>"
`;