Add simplified tryout syntax for release notes · code/app-vscode@b8e6416 · GitHub
Skip to content

Commit b8e6416

Browse files
committed
Add simplified tryout syntax for release notes
1 parent 2be8d96 commit b8e6416

11 files changed

Lines changed: 353 additions & 65 deletions

File tree

‎.github/skills/feature-tryouts/SKILL.md‎

Lines changed: 4 additions & 4 deletions

‎src/vs/workbench/contrib/onboarding/README.md‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,15 +294,25 @@ The events respect existing usage-telemetry settings and policies. Register thei
294294

295295
## Release-note markup
296296

297-
Release notes use standard conditional comments and command links:
297+
Release-note tryout links use a backtick-wrapped inline-code shorthand, without conditional comments:
298+
299+
```md
300+
`try(myFeature.guide,Try My Feature)`
301+
```
302+
303+
The syntax is `try(<tryout-id>,<link-text>)`. The first argument is a stable registered tryout ID, not a command ID. The required link text is presentation only; all executable behavior and availability checks remain in installed code. Surrounding argument whitespace is trimmed. The first comma separates the ID from the label, and the final closing parenthesis ends the token, so labels can contain commas and parentheses. Labels are literal text, not Markdown or HTML.
304+
305+
Only a complete inline-code token is recognized. Bare text, code blocks, and code inside links or raw HTML remain noninteractive. Malformed shorthand stays literal code; a valid but unknown ID shows the existing unavailable-example explanation. Renderers without tryout support display the shorthand as literal inline code.
306+
307+
Use **Developer: Copy Feature Example Link** to produce the shorthand with the installed title as its initial label. The command chooses a longer backtick delimiter when the title contains backticks. Authors may edit the label without changing the registered behavior.
308+
309+
Existing encoded command links remain supported and retain their product-generated `Try This: <title>` labels:
298310

299311
```md
300-
<!-- %IF TRYOUTS %
301312
[Try My Feature](command:workbench.action.onboarding.tryFeature?%5B%22myFeature.guide%22%5D)
302-
%ENDIF % -->
303313
```
304314

305-
Only the stable ID belongs in Markdown. Use **Developer: Copy Feature Example Link** to produce the encoded link. For manual validation, add the link to [releaseNotesTryouts.md](test/browser/fixtures/releaseNotesTryouts.md), open that file, and run **Developer: Open Current File as Release Notes**.
315+
For manual validation, add the shorthand to [releaseNotesTryouts.md](test/browser/fixtures/releaseNotesTryouts.md), open that file, and run **Developer: Open Current File as Release Notes**.
306316

307317
## Choosing the interaction level
308318

‎src/vs/workbench/contrib/onboarding/browser/onboardingTryout.contribution.ts‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { CancellationToken } from '../../../../base/common/cancellation.js';
7-
import { createMarkdownCommandLink } from '../../../../base/common/htmlContent.js';
7+
import { appendEscapedMarkdownInlineCode } from '../../../../base/common/htmlContent.js';
88
import { Event, Relay } from '../../../../base/common/event.js';
99
import { Lazy } from '../../../../base/common/lazy.js';
1010
import { Disposable, isDisposable } from '../../../../base/common/lifecycle.js';
@@ -137,12 +137,7 @@ registerAction2(class extends Action2 {
137137
const id = await pickTryout(accessor);
138138
const scenario = id && tryoutService.getTryout(id);
139139
if (scenario) {
140-
await clipboardService.writeText(createMarkdownCommandLink({
141-
id: RUN_ONBOARDING_TRYOUT_COMMAND_ID,
142-
arguments: [scenario.id],
143-
text: scenario.tryout.title,
144-
tooltip: scenario.tryout.description,
145-
}));
140+
await clipboardService.writeText(appendEscapedMarkdownInlineCode(`try(${scenario.id},${scenario.tryout.title})`));
146141
}
147142
}
148143
});

‎src/vs/workbench/contrib/onboarding/test/browser/fixtures/releaseNotesTryouts.md‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,28 @@ Use this pattern when the behavior can be prepared safely with isolated sample d
1212

1313
The comparison uses bundled sample text, not workspace files. In the diff editor, use **Diff View** to select Inline, Side by Side, or Automatic, then resize the editor.
1414

15-
<!-- %IF TRYOUTS %
16-
[Try Smart Diff Layout](command:workbench.action.onboarding.tryFeature?%5B%22editor.smart-diff%22%5D)
17-
%ENDIF % -->
15+
`try(editor.smart-diff,Try Smart Diff Layout)`
1816

1917
## Guide me: Model and provider selection
2018

2119
Use this pattern for a control whose choices have important capability, speed, provider, or billing implications.
2220

2321
The action opens a new unsent composer in the Agents window, highlights the model picker, and opens its details. Review the available models and providers, but close the picker without selecting anything if you want to leave your current model unchanged. The example never sends a prompt or chooses a billing provider for you.
2422

25-
<!-- %IF TRYOUTS %
26-
[Explore Models and Providers](command:workbench.action.onboarding.tryFeature?%5B%22chat.model-provider-selection%22%5D)
27-
%ENDIF % -->
23+
`try(chat.model-provider-selection,Explore Models and Providers)`
2824

2925
## Compatibility: existing release-note behavior
3026

27+
The same example can use different shorthand labels while a legacy link keeps its product-generated label:
28+
29+
`try(editor.smart-diff,Compare Inline, Side by Side, and Automatic (Read-Only))`
30+
31+
[Legacy Smart Diff Example](command:workbench.action.onboarding.tryFeature?%5B%22editor.smart-diff%22%5D)
32+
33+
Malformed shorthand remains literal code: `try(editor.smart-diff,)`. A well-formed unknown ID has an inert explanation:
34+
35+
`try(unknown.release-notes-example,Unavailable Example)`
36+
3137
The existing settings chip `setting(editor.wordWrap:on)` remains interactive. The Command Palette shortcut is `kb(workbench.action.showCommands)`.
3238

3339
[Open Word Wrap Setting](command:workbench.action.openSettings?%5B%22%40id%3Aeditor.wordWrap%22%5D)

‎src/vs/workbench/contrib/onboarding/test/browser/onboardingTryoutContribution.test.ts‎

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import { Schemas } from '../../../../../base/common/network.js';
1111
import { upcastPartial } from '../../../../../base/test/common/mock.js';
1212
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
1313
import { ITextModelContentProvider, ITextModelService } from '../../../../../editor/common/services/resolverService.js';
14+
import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';
1415
import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js';
1516
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
1617
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
18+
import { IQuickInputService, IQuickPickItem } from '../../../../../platform/quickinput/common/quickInput.js';
1719
import { OnboardingTryoutContribution } from '../../browser/onboardingTryout.contribution.js';
18-
import { onboardingTryoutPresentationRegistry } from '../../common/onboardingTryout.js';
20+
import { IOnboardingTryoutScenario, IOnboardingTryoutService, onboardingTryoutPresentationRegistry, RUN_ONBOARDING_TRYOUT_COMMAND_ID } from '../../common/onboardingTryout.js';
1921

2022
suite('Lazy tryout presentations', () => {
2123
const store = ensureNoDisposablesAreLeakedInTestSuite();
@@ -62,3 +64,77 @@ suite('Lazy tryout presentations', () => {
6264
});
6365
});
6466
});
67+
68+
suite('Copy feature example link', () => {
69+
const store = ensureNoDisposablesAreLeakedInTestSuite();
70+
let instantiation: TestInstantiationService;
71+
let scenarios: Map<string, IOnboardingTryoutScenario>;
72+
let copied: string[];
73+
let items: readonly IQuickPickItem[];
74+
let selection: 'first' | 'cancel' | 'remove';
75+
76+
function register(id: string, title: string): void {
77+
scenarios.set(id, {
78+
id,
79+
trigger: { kind: 'command', commandId: RUN_ONBOARDING_TRYOUT_COMMAND_ID },
80+
presentation: { kind: 'test', payload: {} },
81+
tryout: { title, description: 'A local description.' },
82+
});
83+
}
84+
85+
setup(() => {
86+
instantiation = store.add(new TestInstantiationService());
87+
scenarios = new Map();
88+
copied = [];
89+
items = [];
90+
selection = 'first';
91+
register('sample', 'Local Example');
92+
register('hidden', 'Hidden Example');
93+
instantiation.stub(IOnboardingTryoutService, {
94+
getTryouts: () => [...scenarios.values()],
95+
getTryout: id => scenarios.get(id),
96+
getAvailability: id => id === 'hidden' ? { kind: 'hidden' } : { kind: 'ready' },
97+
run: () => assert.fail('Copying a link must not run the example'),
98+
});
99+
instantiation.stub(IClipboardService, { writeText: async text => { copied.push(text); } });
100+
instantiation.stub(IQuickInputService, {}, 'pick', async (choices: readonly IQuickPickItem[]) => {
101+
items = choices;
102+
if (selection === 'remove') {
103+
scenarios.delete('sample');
104+
}
105+
return selection === 'cancel' ? undefined : choices[0];
106+
});
107+
});
108+
109+
teardown(() => sinon.restore());
110+
111+
async function copy(): Promise<void> {
112+
await instantiation.invokeFunction(CommandsRegistry.getCommand('developer.onboarding.copyTryoutLink')!.handler);
113+
}
114+
115+
for (const [title, markdown] of [
116+
['Local Example', '`try(sample,Local Example)`'],
117+
['Compare A, B (Read-Only) & <C>', '`try(sample,Compare A, B (Read-Only) & <C>)`'],
118+
['Compare `model` and ``provider``', '```try(sample,Compare `model` and ``provider``)```'],
119+
]) {
120+
test(`copies shorthand with the installed title: ${title}`, async () => {
121+
register('sample', title);
122+
await copy();
123+
assert.deepStrictEqual({
124+
copied,
125+
items: items.map(item => ({ label: item.label, description: item.description, detail: item.detail })),
126+
}, {
127+
copied: [markdown],
128+
items: [{ label: title, description: 'sample', detail: 'A local description.' }],
129+
});
130+
});
131+
}
132+
133+
for (const result of ['cancel', 'remove'] as const) {
134+
test(`does not overwrite the clipboard when selection ends with ${result}`, async () => {
135+
selection = result;
136+
await copy();
137+
assert.deepStrictEqual(copied, []);
138+
});
139+
}
140+
});

‎src/vs/workbench/contrib/update/browser/releaseNotesAccessibilityHelp.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class ReleaseNotesAccessibilityHelp implements IAccessibleViewImplementat
3232
() => [
3333
localize('releaseNotes.help.overview', "You are in the release notes. Use your screen reader's reading and heading navigation commands to explore the document and its table of contents."),
3434
localize('releaseNotes.help.navigation', "Press Tab or Shift+Tab to move between links, settings, the update preference checkbox, and available setup actions. Press Enter to activate a link. Press Space or Enter to activate a setup button."),
35-
localize('releaseNotes.help.tryouts', "Try This links open locally registered feature examples. Reading, hovering, or focusing a link does not run it. If an example is unavailable, its explanation and any available setup action appear beside it. Setup does not automatically run the example."),
35+
localize('releaseNotes.help.tryouts', "Feature example links may use custom labels and open locally registered examples. Reading, hovering, or focusing a link does not run it. If an example is unavailable, its explanation and any available setup action appear beside it. Setup does not automatically run the example."),
3636
localize('releaseNotes.help.windows', "Some actions open another editor or window, including the Agents window. Chat examples prepare an input for you to review and do not send it automatically."),
3737
localize('releaseNotes.help.find', "Use {0} to find text in the release notes. The Find input has its own accessibility help.", '<keybinding:editor.action.webvieweditor.showFind>'),
3838
localize('releaseNotes.help.close', "Press Escape to close this help and return focus to the release notes."),

‎src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts‎

Lines changed: 12 additions & 1 deletion

0 commit comments

Comments
 (0)