Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
34 changes: 23 additions & 11 deletions .claude/skills/add-action-and-hook/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export {
} from './<category>/get-xxx';
```

### 1.4 Document the action

Add `@public` JSDoc to the action and to `GetXxxOptions` so they show up in the auto-generated reference. Follow the [`document-public-api`](../document-public-api/SKILL.md) skill for the exact tag set, allowed `@category` values, and style rules (one-sentence summaries, `{@link X}` syntax for type cells, `@expand` for options-bag flattening, `@sample` for code examples).

---

## Step 2: Create the Query or Mutation in `appkit` (for get actions only)
Expand Down Expand Up @@ -219,12 +223,16 @@ Add to `packages/appkit-react/src/features/<category>/index.ts`:
export { useXxx, type UseXxxParameters, type UseXxxReturnType } from './hooks/use-xxx';
```

### Document the hook

Tag the hook (and `UseXxxParameters` / `UseXxxReturnType` if useful) with `@public` JSDoc. Use `@category Hook` and `@section <Domain>`. The full ruleset (required tags, allowed values, `{@link}` linking, `@sample` placeholders) is in the [`document-public-api`](../document-public-api/SKILL.md) skill.

---

## Step 4: Add Examples and Tests

### 4.1 Action example
Create `demo/examples/src/appkit/actions/<category>/get-xxx.ts`:
Create `docs/examples/src/appkit/actions/<category>/get-xxx.ts`:
```ts
import type { AppKit } from '@ton/appkit';
import { getXxx } from '@ton/appkit';
Expand All @@ -237,10 +245,12 @@ export const getXxxExample = async (appKit: AppKit) => {
};
```

Export it in `demo/examples/src/appkit/actions/<category>/index.ts`.
Export it in `docs/examples/src/appkit/actions/<category>/index.ts`.

The `// SAMPLE_START: GET_XXX … // SAMPLE_END: GET_XXX` block is what `@sample docs/examples/src/appkit/actions/<category>#GET_XXX` in the action's JSDoc will pull into the reference (see [`document-public-api`](../document-public-api/SKILL.md)).

### 4.2 Hook example
Create `demo/examples/src/appkit/hooks/<category>/use-xxx.tsx`:
Create `docs/examples/src/appkit/hooks/<category>/use-xxx.tsx`:
```tsx
import { useXxx } from '@ton/appkit-react';

Expand All @@ -252,7 +262,7 @@ export const UseXxxExample = () => {
};
```

Export it in `demo/examples/src/appkit/hooks/<category>/index.ts`.
Export it in `docs/examples/src/appkit/hooks/<category>/index.ts`.

### 4.3 Write tests
**Important:** Do NOT create a new test file per example. Add tests to the existing `<category>.test.ts` / `<category>.test.tsx` file in the same directory.
Expand Down Expand Up @@ -280,24 +290,26 @@ If the mock `appKit` in `beforeEach` doesn't have the required mocks (e.g., `get

## Step 5: Update Templates and Docs

### 5.1 Update action template
Edit `template/appkit-actions.md`, add after the nearest related action:
The reference at `packages/<pkg>/docs/reference.md` is fully generated from `@public` JSDoc, so once Step 1.4 / Step 3.1 are done your action and hook are already in the reference. The two steps below update the older hand-curated `actions.md` / `hooks.md` listings.

### 5.1 Update action listing
Edit `docs/templates/packages/appkit/docs/actions.md`, add after the nearest related action:
```md
### `getXxx`

Description of what the action does.

%%demo/examples/src/appkit/actions/<category>#GET_XXX%%
%%docs/examples/src/appkit/actions/<category>#GET_XXX%%
```

### 5.2 Update hooks template
Edit `template/appkit-hooks.md`, add after the nearest related hook:
### 5.2 Update hooks listing
Edit `docs/templates/packages/appkit-react/docs/hooks.md`, add after the nearest related hook:
```md
### `useXxx`

Hook to ...

%%demo/examples/src/appkit/hooks/<category>#USE_XXX%%
%%docs/examples/src/appkit/hooks/<category>#USE_XXX%%
```

### 5.3 Run quality check
Expand All @@ -310,4 +322,4 @@ All tests and type checks must pass before continuing.
```bash
pnpm docs:update
```
Verify the relevant `.md` files in `packages/appkit/docs/` and `packages/appkit-react/docs/` were updated.
Runs `docs:reference` (regenerates `reference.md` from `@public` JSDoc) followed by `docs:template` (resolves `%%path#SAMPLE%%` placeholders into real code blocks). Verify the resulting `.md` files in `packages/appkit/docs/` and `packages/appkit-react/docs/` were updated.
10 changes: 10 additions & 0 deletions .claude/skills/add-ui-component/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,13 @@ Use `packages/appkit-react/src/components/block` as the canonical reference for:
- `block.tsx`
- `block.module.css`
- `block.stories.tsx`

---

## 5. Documentation

If the component is meant to be part of the public API, tag it with `@public` JSDoc so it shows up in the auto-generated reference (`packages/appkit-react/docs/reference.md`). Use `@category Component` and `@section <Domain>`. The full ruleset (required tags, allowed values, `{@link X}` for type-cell links, single-sentence description style, `@sample` for usage examples) lives in the [`document-public-api`](../document-public-api/SKILL.md) skill.

For compound components (`const Foo = { Container: …, Item: … }`), the same `@public` block on the namespace object is enough — the generator walks each member and renders them as `#### Foo.Container`, `#### Foo.Item`, etc.

After editing JSDoc run `pnpm docs:update` to regenerate `packages/appkit-react/docs/reference.md`.
224 changes: 224 additions & 0 deletions .claude/skills/document-public-api/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
---
name: document-public-api
description: How to write JSDoc for public symbols so they appear in the auto-generated reference (`packages/<pkg>/docs/reference.md`). Use whenever adding `@public` to an export or editing the surrounding doc-comment.
---

# Documenting public API for the reference generator

The generator at `docs/reference-generator/` walks every export of `packages/appkit` and `packages/appkit-react`, picks the ones tagged `@public`, and renders them into `packages/<pkg>/docs/reference.md`. The output goes through `docs/template-resolver/` which resolves `%%path#NAME%%` placeholders into real code from `docs/examples/`.

This skill describes exactly which JSDoc tags the generator understands, in what shape, and what the rendered output looks like.

---

## Required tags

Every `@public` symbol **must** also declare:

- `@public` — opt-in marker. Without it the symbol is invisible to the generator.
- `@category <Class | Action | Hook | Component | Type | Constants>` — top-level group (`## Class`, `## Action`, …). The generator validates the value; anything else is an error.
- `@section <Domain>` — second-level group (`### Balances`, `### Connectors and wallets`, …). Free-form string; symbols sharing the same value end up under one heading.

If any of the three is missing, `pnpm docs:reference` aborts with a list of offending symbols.

### Which declaration shape fits each `@category`

| `@category` | Allowed declaration |
| --- | --- |
| `Class` | `export class X { }` |
| `Action` / `Hook` | `export function name(...)` or `export const name = (...) => ...` |
| `Component` | Function returning JSX **or** a `const X = { Sub: …, Sub2: … }` object of FCs (rendered as a compound component) |
| `Type` | `export interface X { }` or `export type X = ...` |
| `Constants` | `export const X = { ... } as const` (or any `export const X = literal`) |

Putting `@category Class` on an interface raises `[X] @category Class requires the symbol to be a class declaration.` at generate time. `Type` and `Constants` are deliberately split so a `const X = {}` cannot accidentally land under "Type" — pick `Constants` for runtime values, `Type` for compile-time-only declarations.

---

## Optional tags

- `@param <name> - <description>` — column row in the parameters table. Description should be a single self-contained sentence with a trailing period.
- `@returns <description>` — appears as `Returns: \`Type\` — <description>.`
- `@example` — inline TS/TSX code block printed under the entry.
- `@sample <dir/path>#<SAMPLE_NAME>` — placeholder that `pnpm docs:template` replaces with the body of a `// SAMPLE_START: NAME … // SAMPLE_END: NAME` block under `docs/examples/`. Multiple `@sample` tags are allowed.
- `@expand <paramName>` — for actions that take an options-bag (`getBalanceByAddress(appKit, options)`), expands the named parameter's fields into extra rows like `options.address`, `options.network`. Without `@expand`, the parameter is shown as one row.
- `@extract` — for type aliases that re-export a type from another package (typically walletkit). The renderer follows the alias to the original `interface` / `type` and uses **its** structure (field table or code block). `@public`/`@category`/`@section` still live on the alias; the source's JSDoc supplies field-level descriptions. See "Re-exporting from walletkit" below.
- `@title <Override>` — override the top-level heading for this single symbol. Rarely needed; usually omit.

`@param` accepts a `{@link X}`-as-type-override at the very start of its description (see below); `@returns` does **not** — see the warning in that section.

---

## Cross-reference syntax: `{@link X}`

`{@link X}` becomes a markdown link to the `#x` anchor in the same reference. It works in two places:

1. **Anywhere in a description** (summary, field doc, `@param` description, `@returns` description). The text reads `... see {@link getBalance} ...` and renders `... see [\`getBalance\`](#getbalance) ...`.

2. **At the very start of `@param` description** — the link is extracted and used as the **Type column** for that row. The text after the link goes into the Description column.

```ts
@param config - {@link AppKitConfig} Networks, connectors, providers and runtime flags.
```

renders as:

| Parameter | Type | Description |
| --- | --- | --- |
| `config`* | [`AppKitConfig`](#appkitconfig) | Networks, connectors, providers and runtime flags. |

The TS-inferred type is replaced by the link. Use this when the inferred type is verbose or you want a cleaner cell.

⚠ **Don't put `{@link X}` at the start of `@returns`.** ts-morph's JSDoc parser interprets a leading `{…}` as a legacy type annotation (`@returns {Type} desc`) and silently drops it from the comment text — both the type-override and the description disappear. Write the description as plain prose; the inferred return type is auto-linked anyway. If you really want to mention a type, put `{@link X}` mid-sentence: `@returns The wallet response carrying …`.

`X` must name another `@public` symbol in the same reference — the generator does not validate this, so a typo or an undocumented target produces a dead link.

---

## Style rules

- **One sentence per description.** Summary, `@param` description, field doc, `@returns` description — all collapse onto one line in the rendered table. Multi-paragraph JSDoc reads as a long ugly run-on. If you need a second clause, join with `;` or `—`.
- **Self-contained sentences after `{@link X}`.** Capitalize the first word, end with a period. Don't write `{@link X} with foo` (renders as Description = `with foo` — fragment); write `{@link X} Foo and bar.`.
- **No bullet lists, no fenced code, no tables in JSDoc descriptions.** They survive the markdown but break table rendering.
- **No outright fabrication.** Don't claim methods or behavior that don't exist in the code; the reference is pulled directly from the JSDoc and any error there ships to readers.

---

## Worked example

```ts
/**
* Read the Toncoin balance of an arbitrary address — useful for wallets that aren't selected in AppKit (use {@link getBalance} for the selected wallet).
*
* @param appKit - {@link AppKit} Runtime instance.
* @param options - {@link GetBalanceByAddressOptions} Target address and optional network.
* @returns Balance in TON as a human-readable decimal string.
*
* @sample docs/examples/src/appkit/actions/balances#GET_BALANCE_BY_ADDRESS
* @expand options
*
* @public
* @category Action
* @section Balances
*/
export const getBalanceByAddress = async (
appKit: AppKit,
options: GetBalanceByAddressOptions,
): Promise<GetBalanceByAddressReturnType> => { /* ... */ };
```

```ts
/**
* Constructor options for {@link AppKit} — networks, connectors, providers and runtime flags.
*
* @public
* @category Type
* @section Core
*/
export interface AppKitConfig {
/** Map of chain id to api-client config; if omitted, AppKit defaults to mainnet only. */
networks?: NetworkAdapters;

/** Wallet connectors registered at startup. */
connectors?: ConnectorInput[];

/** Default network connectors enforce on new connections; `undefined` to allow any. */
defaultNetwork?: Network;

/** Defi/onramp providers registered at startup. */
providers?: ProviderInput[];
}
```

---

## Re-exporting from walletkit (`@extract`)

Some types live in `@ton/walletkit` but are part of the `@ton/appkit` public API (e.g. `Network`, `NetworkAdapters`, `NetworkConfig`, `ApiClientConfig`). A bare `export { Network } from '@ton/walletkit'` will **not** appear in the appkit reference — `collect.ts` filters out symbols whose declaration lives outside the package. Use a local type alias plus `@extract` to surface them:

```ts
// packages/appkit/src/types/network.ts
import type { NetworkAdapters as WalletkitNetworkAdapters } from '@ton/walletkit';

/**
* @extract
* @public
* @category Type
* @section Networks
*/
export type NetworkAdapters = WalletkitNetworkAdapters;
```

What happens at generate time:

1. The alias declaration sits in `appkit/src/`, so the package-boundary filter passes it.
2. `@extract` tells `extractType` to follow the alias to the underlying walletkit `interface`/`type` and reuse its shape — fields show up in the reference table, JSDoc on each field is pulled from walletkit.
3. `@public`/`@category`/`@section` are read from the alias (you control where it appears in the appkit reference, not walletkit).
4. If the alias has its own summary on the JSDoc block, that takes precedence over walletkit's.

For declaration-merged symbols (a value + same-named interface, like `Network`), keep the value side as a separate `export const`:

```ts
import type { Network as WalletkitNetwork } from '@ton/walletkit';
import { Network as WalletkitNetworkValue } from '@ton/walletkit';

/**
* @extract
* @public
* @category Type
* @section Networks
*/
export type Network = WalletkitNetwork;

// Value side — `Network.mainnet()` etc.
export const Network = WalletkitNetworkValue;
```

The cleanest form is a JSDoc-tagged ExportDeclaration — one block tags every symbol inside the same `export { … }`:

```ts
/**
* @extract
* @public
* @category Class
* @section Swap
*/
export { SwapError, SwapProvider, SwapManager } from '@ton/walletkit';

/**
* @extract
* @public
* @category Type
* @section Swap
*/
export type { SwapToken, TokenAmount, SwapParams } from '@ton/walletkit';
```

Group symbols by `(category, section)` — one ExportDeclaration per group keeps the JSDoc shared.

**Rebuild walletkit after editing its JSDoc.** ts-morph resolves cross-package symbols through `dist/.../*.d.ts`, not the source `*.ts`, so JSDoc edits in `packages/walletkit/src/...` only land in the reference after `pnpm --filter @ton/walletkit build`. For appkit-only edits (no walletkit changes), `pnpm docs:update` alone is enough.

**Important**: `@extract` does NOT make a wildcard `export * from '@ton/appkit'` (used in appkit-react) leak appkit symbols into appkit-react. Wildcard re-exports cannot carry JSDoc, so they cannot carry `@extract`, so the boundary filter still drops them. The opt-in is local and explicit.

---

## After editing JSDoc

Run `pnpm docs:update` (alias for `pnpm docs:reference && pnpm docs:template`). The first step regenerates `docs/templates/packages/<pkg>/docs/reference.md`; the second resolves `@sample` placeholders into real code blocks and writes the final `packages/<pkg>/docs/reference.md`.

If validation fails, the script prints every problem in one go — fix them all before re-running.

---

## Quick checklist

- [ ] `@public` present
- [ ] `@category` set to one of `Class`, `Action`, `Hook`, `Component`, `Type`, `Constants`
- [ ] `@section` set to a domain string (matches existing entries where appropriate)
- [ ] Summary is one sentence with a trailing period
- [ ] Each `@param` description is one self-contained sentence
- [ ] `{@link X}` only used for symbols that are themselves `@public`
- [ ] `@expand` used for any options-bag parameter you want flattened
- [ ] `@extract` used for type aliases that re-export a walletkit type
- [ ] `@sample` points to a real `// SAMPLE_START: NAME` block in `docs/examples/`
- [ ] `pnpm docs:update` runs cleanly
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,8 @@ describe('Balance Actions Examples (Integration)', () => {

appKit.walletsManager.setWallets([mockWallet]);

// Setup balance response
// Using a simple object that mimics TokenAmount to avoid constructor issues if any,
// or we could use `new TokenAmount(1000000000n, 9)` if available.
// Since we test the example which calls .toString(), this is sufficient.
const balanceValue = { toString: () => '1000000000' } as TokenAmount;
// Setup balance response. TokenAmount is a string alias.
const balanceValue: TokenAmount = '1000000000';
mockGetBalance.mockResolvedValue(balanceValue);

await getBalanceExample(appKit);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { getBalanceByAddress } from '@ton/appkit';
export const getBalanceByAddressExample = async (appKit: AppKit) => {
// SAMPLE_START: GET_BALANCE_BY_ADDRESS
const balanceByAddress = await getBalanceByAddress(appKit, {
address: 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c', // Zero Address
address: 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c',
});
console.log('Balance by address:', balanceByAddress.toString());
console.log('Balance by address:', balanceByAddress);
// SAMPLE_END: GET_BALANCE_BY_ADDRESS
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const getBalanceExample = async (appKit: AppKit) => {
// SAMPLE_START: GET_BALANCE
const balance = await getBalance(appKit);
if (balance) {
console.log('Balance:', balance.toString());
console.log('Balance:', balance);
}
// SAMPLE_END: GET_BALANCE
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { createTonConnectConnector } from '@ton/appkit';

export const addConnectorExample = (appKit: AppKit) => {
// SAMPLE_START: ADD_CONNECTOR
const stopWatching = addConnector(
const unregister = addConnector(
appKit,
createTonConnectConnector({
tonConnectOptions: {
Expand All @@ -21,7 +21,7 @@ export const addConnectorExample = (appKit: AppKit) => {
}),
);

// Later: stopWatching();
// Later: unregister();
// SAMPLE_END: ADD_CONNECTOR
return stopWatching;
return unregister;
};
Loading
Loading