# Vscode Window Messages

> Guidelines for using vscode.window.show*Message methods. Use when working with showInformationMessage, showWarningMessage, showErrorMessage.

- Skill: `forcedotcom/vscode-window-messages` (Agent Skill)
- Install (CLI): `npx skillmds@latest add forcedotcom/vscode-window-messages`
- Raw SKILL.md: https://api.skillmd.com/api/skills/forcedotcom/vscode-window-messages/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: forcedotcom (https://skillmd.com/u/forcedotcom)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/forcedotcom/vscode-window-messages

---


# VSCode Window Messages Best Practices

## Quick Reference: Critical Rules

| Category         | DO                                      | DON'T                                            |
| ---------------- | --------------------------------------- | ------------------------------------------------ |
| API              | Direct `vscode.window.show*Message`     | Legacy `NotificationService`                     |
| Messages         | `nls.localize('key', ...args)`          | String literals or template literals without nls |
| Return Values    | Handle `Thenable<string \| undefined>` or `Thenable<MessageItem \| undefined>` | Ignore return values                             |
| Button Actions   | Check return value for button clicks    | Assume user always clicks                        |
| Button Actions   | `nls.localize`                          | String literals or template literals without nls |
| Modal Options    | `{ modal: true, detail: ... }` for blocking dialogs | `detail` without `modal: true` (detail modal-only); explicit 'Cancel' buttons (VS Code adds one automatically) |
| Effect (wait)    | `Effect.promise()` when response needed | `Effect.promise()` for fire-and-forget           |
| Effect (no wait) | `Effect.sync()` for fire-and-forget     | `Effect.promise()` when not waiting              |

## Use Direct vscode.window Calls

Use `vscode.window.show*Message` directly. Don't use legacy `NotificationService` in new code.

```typescript
import * as vscode from 'vscode';
import { nls } from '../messages/messages';

// CORRECT
await vscode.window.showInformationMessage(nls.localize('retrieve_canceled'));
await vscode.window.showErrorMessage(nls.localize('retrieve_failed', String(error)));

// WRONG
await notificationService.showInformationMessage(nls.localize('retrieve_canceled'));
```

## Internationalization with nls.localize()

All message strings and button labels must use `nls.localize()`. Enforced by `no-vscode-message-literals` ESLint rule.

**Button labels should use `nls.localize()`** even for command titles. Importing `package.nls.json` directly strips locale context—only manifest `%key%` reads locale-specific JSON files. Use `nls.localize()` to preserve Japanese and other locales via `i18n.ja.ts`.

```typescript
// CORRECT
await vscode.window.showInformationMessage(nls.localize('retrieve_canceled'));
await vscode.window.showErrorMessage(nls.localize('retrieve_failed', String(error)));
await vscode.window.showInformationMessage(`${nls.localize('prefix')} ${nls.localize('suffix')}`);

const answer = await vscode.window.showWarningMessage(
  nls.localize('confirm_delete'),
  nls.localize('yes_button'),
  nls.localize('no_button')
);

// WRONG
await vscode.window.showInformationMessage('Operation successful');
await vscode.window.showInformationMessage(`Operation ${status} successful`);
await vscode.window.showWarningMessage(nls.localize('confirm_delete'), 'Yes', 'No');
```

**Adding new messages:**

1. Add key/value to `i18n.ts` in package
2. Use `nls.localize('your_message_key', ...args)`

## Handling Return Values

Returns clicked button or `undefined` if dismissed.

**Return types:**
- String buttons: `Thenable<string | undefined>`
- MessageItem buttons: `Thenable<MessageItem | undefined>`

```typescript
// CORRECT - String buttons
const selection = await vscode.window.showWarningMessage(
  nls.localize('unsaved_changes'),
  nls.localize('save_button'),
  nls.localize('discard_button')
);

if (selection === nls.localize('save_button')) {
  await saveFile();
}

// CORRECT - MessageItem buttons
const item = await vscode.window.showWarningMessage(
  nls.localize('unsaved_changes'),
  { modal: true },
  { title: nls.localize('save_button') },
  { title: nls.localize('discard_button') }
);

if (item?.title === nls.localize('save_button')) {
  await saveFile();
}

// CORRECT - Fire and forget
void vscode.window.showInformationMessage(nls.localize('operation_completed'));

// WRONG
vscode.window.showInformationMessage(nls.localize('operation_completed')); // Missing void
```

## Integration with Effect

### When User Response is Required

Use `Effect.promise()` to wait for user response. Blocks execution until user responds.

```typescript
import { Effect } from 'effect';

// CORRECT - Wait for response
const selection =
  yield *
  Effect.promise(() =>
    vscode.window.showWarningMessage(nls.localize('confirm_action'), nls.localize('yes'), nls.localize('no'))
  );

if (selection === nls.localize('yes')) {
  yield * performAction();
}

// CORRECT - Wait for error acknowledgment
yield * Effect.promise(() => vscode.window.showErrorMessage(nls.localize('critical_error')));
```

### When User Response is NOT Required (Fire-and-Forget)

Use `Effect.sync()` for non-blocking messages. Execution continues immediately.

```typescript
import { Effect } from 'effect';

// CORRECT - Fire and forget
yield *
  Effect.sync(() => {
    void vscode.window.showInformationMessage(nls.localize('background_task_started'));
  });

yield * performBackgroundTask();

// WRONG - Blocks unnecessarily
yield * Effect.promise(() => vscode.window.showInformationMessage(nls.localize('background_task_started')));

// WRONG - Direct call in Effect.gen
yield *
  Effect.gen(function* () {
    await vscode.window.showErrorMessage('Error'); // Type error
  });
```

## Message Types

- **`showInformationMessage`**: Success, info, non-critical
- **`showWarningMessage`**: Warnings, recoverable errors, user decisions
- **`showErrorMessage`**: Errors, failures, critical issues

```typescript
await vscode.window.showInformationMessage(nls.localize('retrieve_completed'));
await vscode.window.showWarningMessage(nls.localize('unsaved_changes'));
await vscode.window.showErrorMessage(nls.localize('retrieve_failed', errorMessage));

// Modal with detail (detail only shown for modal)
await vscode.window.showWarningMessage(
  nls.localize('destructive_action_warning'),
  { modal: true, detail: nls.localize('destructive_action_detail') },
  nls.localize('confirm')
);
```

**Note:** VS Code automatically adds a 'Cancel' button to modal dialogs. Do not add an explicit `nls.localize('cancel_button')` as an item when `modal: true`. Dismissing the dialog (via 'Cancel' or ESC) returns `undefined`.

## MessageOptions and MessageItem

### MessageOptions

- **`modal?: boolean`** - System modal dialog, blocks interaction
- **`detail?: string`** - Extra text (modal only)

### Button Types

Strings or `MessageItem` objects:

```typescript
// String buttons (prefer)
const result = await vscode.window.showWarningMessage(
  nls.localize('confirm_action'),
  nls.localize('yes'),
  nls.localize('no')
);
// result: string | undefined

// MessageItem (for modal ESC handling)
const result = await vscode.window.showWarningMessage(
  nls.localize('confirm_action'),
  { modal: true },
  { title: nls.localize('yes'), isCloseAffordance: false },
  { title: nls.localize('no'), isCloseAffordance: true }
);
// result: MessageItem | undefined
```

**`isCloseAffordance`**: `true` = button handles ESC. Modal-only. Modals auto-add Cancel; use this to control which custom button handles ESC.

## Common Patterns

### Conditional Messages

```typescript
// Fire-and-forget
if (hasErrors) {
  void vscode.window.showErrorMessage(nls.localize('operation_completed_with_errors'));
} else {
  void vscode.window.showInformationMessage(nls.localize('operation_completed_successfully'));
}
```

### User Confirmation (Requires Response)

```typescript
import { Effect } from 'effect';

const confirm =
  yield *
  Effect.promise(() =>
    vscode.window.showWarningMessage(
      nls.localize('confirm_destructive_action'),
      nls.localize('proceed_button')
    )
  );

if (confirm === nls.localize('proceed_button')) {
  yield * performDestructiveAction();
}
```

## ESLint Rule

`no-vscode-message-literals` enforces:

- No string literals as first argument
- No template literals unless they contain `nls.localize()` calls
- Applies to `showInformationMessage`, `showWarningMessage`, `showErrorMessage`

Run `npm run lint` before committing.

