Skip to content

Commit a50dbd0

Browse files
project: widen prettier scope to cover all files
The format:check and format scripts only covered src/ and demo source directories, so a bunch of files e.g. tests were never checked by CI. But, even more annoyingly, I found Claude would sometimes explicitly run `prettier --write` on excluded files that it edited, thus introducing diff noise. Instead, match ably-js's approach of formatting everything (relying on Prettier's automatic ignoral of gitignored stuff [1]). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> [1] https://prettier.io/docs/ignore
1 parent 3e04e96 commit a50dbd0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1375
-1129
lines changed

.claude/rules/ABSTRACTIONS.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,12 @@ ably-common/ # Git submodule — shared protocol resour
3939

4040
The SDK ships four entry points from a single package:
4141

42-
| Export path | Contains | Purpose | External deps |
43-
|---|---|---|---|
44-
| `@ably/ai-transport` | Generic codec interfaces, `createClientTransport`, `createServerTransport`, shared utilities | Core primitives — codec-agnostic transport and encoding | `ably` (peer) |
45-
| `@ably/ai-transport/react` | `useClientTransport`, `useView`, `useTree`, `useSend`, `useRegenerate`, `useEdit`, `useActiveTurns`, `useAblyMessages` | Generic React hooks for any codec | `ably`, `react` (peers) |
46-
| `@ably/ai-transport/vercel` | `UIMessageCodec`, `createServerTransport`, `createClientTransport`, `createChatTransport`, Vercel-specific types | Drop-in Vercel AI SDK integration | `ably`, `ai` (peers) |
47-
| `@ably/ai-transport/vercel/react` | `useChatTransport`, `useMessageSync` | React hooks for Vercel's `useChat` | `ably`, `ai`, `react` (peers) |
48-
42+
| Export path | Contains | Purpose | External deps |
43+
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------------------------- |
44+
| `@ably/ai-transport` | Generic codec interfaces, `createClientTransport`, `createServerTransport`, shared utilities | Core primitives — codec-agnostic transport and encoding | `ably` (peer) |
45+
| `@ably/ai-transport/react` | `useClientTransport`, `useView`, `useTree`, `useSend`, `useRegenerate`, `useEdit`, `useActiveTurns`, `useAblyMessages` | Generic React hooks for any codec | `ably`, `react` (peers) |
46+
| `@ably/ai-transport/vercel` | `UIMessageCodec`, `createServerTransport`, `createClientTransport`, `createChatTransport`, Vercel-specific types | Drop-in Vercel AI SDK integration | `ably`, `ai` (peers) |
47+
| `@ably/ai-transport/vercel/react` | `useChatTransport`, `useMessageSync` | React hooks for Vercel's `useChat` | `ably`, `ai`, `react` (peers) |
4948

5049
## Two-Layer Architecture
5150

@@ -67,11 +66,11 @@ Header/event/message-name constants and Ably message utilities used by both laye
6766

6867
The client-side architecture separates three concerns:
6968

70-
| Component | Owns | Events |
71-
|---|---|---|
72-
| **Tree** | Complete conversation state — every node from live messages and history. Turn tracking (active turn → clientId). | `update` (any structural change), `ably-message` (every raw message), `turn` (start/end) — unfiltered, fires for all changes |
73-
| **View** | Pagination window — which history-loaded nodes are visible vs withheld. | Same event names, but **scoped to the visible window** — only fires when the visible output changes |
74-
| **Transport** | Write path (send/regenerate/edit/cancel), channel subscription, stream routing, decode loop. | `error` only — all data events moved to Tree/View |
69+
| Component | Owns | Events |
70+
| ------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
71+
| **Tree** | Complete conversation state — every node from live messages and history. Turn tracking (active turn → clientId). | `update` (any structural change), `ably-message` (every raw message), `turn` (start/end) — unfiltered, fires for all changes |
72+
| **View** | Pagination window — which history-loaded nodes are visible vs withheld. | Same event names, but **scoped to the visible window** — only fires when the visible output changes |
73+
| **Transport** | Write path (send/regenerate/edit/cancel), channel subscription, stream routing, decode loop. | `error` only — all data events moved to Tree/View |
7574

7675
The Tree is the source of truth. The View subscribes to Tree events and re-emits them filtered to what's visible. The Transport wires the channel to the Tree and exposes both as `transport.tree` and `transport.view`.
7776

@@ -146,8 +145,8 @@ Public-facing entry points (e.g. `createClientTransport()`) are factory function
146145

147146
1. **Two-layer split**: Generic transport/codec knows nothing about Vercel. Vercel layer implements the codec and provides convenience wrappers.
148147
2. **Codec-parameterized**: All generic components are parameterized by `<TEvent, TMessage>` via the `Codec` interface.
149-
4. **Constructor/option injection**: All dependencies passed explicitly — no singletons, no globals.
150-
3. **Composition, not inheritance**: Transports compose features; no class hierarchies.
148+
3. **Constructor/option injection**: All dependencies passed explicitly — no singletons, no globals.
149+
4. **Composition, not inheritance**: Transports compose features; no class hierarchies.
151150
5. **Interface-first**: Public contracts are TypeScript interfaces. Implementations are internal `Default*` classes, exposed to consumers via factory functions.
152151
6. **Header discipline**: Generic layer uses only `x-ably-*` headers. Domain-specific headers (e.g. `x-domain-*`) belong in the Vercel layer.
153152
7. **Explicit exports**: Only types and functions listed in `index.ts` files are public API.

.claude/rules/ERRORS.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Every error should either be handled internally with a clear recovery strategy,
1111
- **Isolate handler callbacks**: When the SDK invokes developer-provided callbacks (event listeners, hooks), wrap each in try/catch. One bad handler shouldn't kill internal machinery or prevent other handlers from firing.
1212
- **Define recovery semantics explicitly**: For every internal operation that can fail, decide upfront: retry, degrade, or propagate. Document the choice.
1313
- **Preserve the original error**: When wrapping errors, always attach the original as `cause`. Developers debugging production issues need the full chain.
14-
- **Best-effort operations should be labeled**: If something is fire-and-forget (e.g. cleanup publish on close), comment it as such. Swallowing an error is only acceptable when failure is unrecoverable *and* non-impactful.
14+
- **Best-effort operations should be labeled**: If something is fire-and-forget (e.g. cleanup publish on close), comment it as such. Swallowing an error is only acceptable when failure is unrecoverable _and_ non-impactful.
1515

1616
### Surfacing errors to developers
1717

@@ -86,9 +86,9 @@ throw new Ably.ErrorInfo('unable to send message; room is not attached', ErrorCo
8686
throw new Ably.ErrorInfo('unable to detach room; room is in failed state', ErrorCode.RoomInInvalidState, 400);
8787

8888
// Wrong — do not use these prefixes
89-
"cannot send message"
90-
"failed to send message"
91-
"Could not send message"
89+
// "cannot send message"
90+
// "failed to send message"
91+
// "Could not send message"
9292
```
9393

9494
Dynamic context is allowed in the message:
@@ -145,12 +145,12 @@ reject(
145145

146146
Use the custom Vitest matchers in a test helper (`test/helper/expectations.ts`):
147147

148-
| Matcher | Usage |
149-
|---|---|
150-
| `toBeErrorInfo({ code?, statusCode?, message?, cause? })` | Assert a value is an `Ably.ErrorInfo` matching the given fields |
151-
| `toThrowErrorInfo({ code?, statusCode?, message? })` | Assert a sync function throws a matching `Ably.ErrorInfo` |
152-
| `toBeErrorInfoWithCode(code)` | Shorthand — assert value is `Ably.ErrorInfo` with a specific code |
153-
| `toThrowErrorInfoWithCode(code)` | Shorthand — assert sync function throws with a specific code |
154-
| `toBeErrorInfoWithCauseCode(code)` | Assert value is `Ably.ErrorInfo` whose `.cause.code` matches |
148+
| Matcher | Usage |
149+
| --------------------------------------------------------- | ----------------------------------------------------------------- |
150+
| `toBeErrorInfo({ code?, statusCode?, message?, cause? })` | Assert a value is an `Ably.ErrorInfo` matching the given fields |
151+
| `toThrowErrorInfo({ code?, statusCode?, message? })` | Assert a sync function throws a matching `Ably.ErrorInfo` |
152+
| `toBeErrorInfoWithCode(code)` | Shorthand — assert value is `Ably.ErrorInfo` with a specific code |
153+
| `toThrowErrorInfoWithCode(code)` | Shorthand — assert sync function throws with a specific code |
154+
| `toBeErrorInfoWithCauseCode(code)` | Assert value is `Ably.ErrorInfo` whose `.cause.code` matches |
155155

156156
The matchers only check the fields you provide — omitted fields are not compared. The `cause` field is checked recursively.

.claude/rules/LOGGING.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ type LogContext = Record<string, any>;
1919

2020
## Log Levels
2121

22-
| Level | When to use |
23-
|---|---|
24-
| `Trace` | Routine operations — entry point of every key method. The most verbose level. |
25-
| `Debug` | Useful for debugging but superfluous in normal operation — successful completions, state transitions, decision points. |
26-
| `Info` | Operationally significant but expected — transport open/close, lifecycle events. |
27-
| `Warn` | Not an error yet, but could cause problems — unexpected but recoverable states. |
28-
| `Error` | An operation has failed and cannot be automatically recovered. |
29-
| `Silent` | No logging. |
22+
| Level | When to use |
23+
| -------- | ---------------------------------------------------------------------------------------------------------------------- |
24+
| `Trace` | Routine operations — entry point of every key method. The most verbose level. |
25+
| `Debug` | Useful for debugging but superfluous in normal operation — successful completions, state transitions, decision points. |
26+
| `Info` | Operationally significant but expected — transport open/close, lifecycle events. |
27+
| `Warn` | Not an error yet, but could cause problems — unexpected but recoverable states. |
28+
| `Error` | An operation has failed and cannot be automatically recovered. |
29+
| `Silent` | No logging. |
3030

3131
Levels are hierarchical. Setting the level to `Debug` suppresses `Trace` but shows everything else.
3232

@@ -85,7 +85,8 @@ this._logger.debug('Tree.upsert(); inserting new node', { msgId, parentId, forkO
8585

8686
// Warning
8787
this._logger.warn('DefaultDecoderCore.decode(); unexpected message action', {
88-
action, serial: message.serial,
88+
action,
89+
serial: message.serial,
8990
});
9091

9192
// Error
@@ -130,7 +131,8 @@ Not yet an error, but something that could cascade:
130131

131132
```ts
132133
this._logger.warn('DefaultDecoderCore.decode(); unrecognized message name', {
133-
name: message.name, serial: message.serial,
134+
name: message.name,
135+
serial: message.serial,
134136
});
135137
```
136138

.claude/rules/TESTS.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
## Two tiers
44

5-
| Tier | Command | Runs against | What it proves |
6-
|---|---|---|---|
7-
| **Unit** | `npm test` | Mocks only | Every code path works correctly in isolation |
8-
| **Integration** | `npm run test:integration` | Real Ably channels | Happy path works end-to-end over real Ably |
5+
| Tier | Command | Runs against | What it proves |
6+
| --------------- | -------------------------- | ------------------ | -------------------------------------------- |
7+
| **Unit** | `npm test` | Mocks only | Every code path works correctly in isolation |
8+
| **Integration** | `npm run test:integration` | Real Ably channels | Happy path works end-to-end over real Ably |
99

1010
Config: `vitest.config.ts` (unit, excludes `*.integration.test.ts`) and `vitest.config.integration.ts` (integration only).
1111

@@ -49,11 +49,11 @@ By default, integration tests run against the **Ably sandbox**. The globalSetup
4949

5050
To run against a different environment, set `VITE_ABLY_ENV`:
5151

52-
| `VITE_ABLY_ENV` | Behaviour | API key required? |
53-
|---|---|---|
54-
| *(unset)* / `sandbox` | Provisions a sandbox app automatically | No |
55-
| `local` | Connects to `local-rest.ably.io:8081` (no TLS) | Yes — set `VITE_ABLY_API_KEY` |
56-
| `production` | Connects to production Ably | Yes — set `VITE_ABLY_API_KEY` |
52+
| `VITE_ABLY_ENV` | Behaviour | API key required? |
53+
| --------------------- | ---------------------------------------------- | ----------------------------- |
54+
| _(unset)_ / `sandbox` | Provisions a sandbox app automatically | No |
55+
| `local` | Connects to `local-rest.ably.io:8081` (no TLS) | Yes — set `VITE_ABLY_API_KEY` |
56+
| `production` | Connects to production Ably | Yes — set `VITE_ABLY_API_KEY` |
5757

5858
### Conventions
5959

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ably-common/
2+
specification/
3+
.claude/skills/

CLAUDE.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ npm run precommit # format:check + lint + typecheck
2323

2424
Detailed guidance lives in `.claude/rules/`:
2525

26-
| Rule file | Covers |
27-
|---|---|
26+
| Rule file | Covers |
27+
| ----------------- | ------------------------------------------------------------------------------------------ |
2828
| `ABSTRACTIONS.md` | Two-layer architecture, directory layout, class pattern, composition, dependency injection |
29-
| `ERRORS.md` | Error type (`Ably.ErrorInfo`), error codes, message format, wrapping, testing |
30-
| `LOGGING.md` | Logger interface, log levels, message format, context propagation |
31-
| `PROMISES.md` | async/await policy, exception handling |
32-
| `TYPES.md` | Type safety rules, import conventions, no `any`/`as`/`!` policy |
33-
| `TESTS.md` | Unit vs integration tests, mocking strategy, coverage expectations |
34-
| `AISDK.md` | Vercel AI SDK v6 specifics |
29+
| `ERRORS.md` | Error type (`Ably.ErrorInfo`), error codes, message format, wrapping, testing |
30+
| `LOGGING.md` | Logger interface, log levels, message format, context propagation |
31+
| `PROMISES.md` | async/await policy, exception handling |
32+
| `TYPES.md` | Type safety rules, import conventions, no `any`/`as`/`!` policy |
33+
| `TESTS.md` | Unit vs integration tests, mocking strategy, coverage expectations |
34+
| `AISDK.md` | Vercel AI SDK v6 specifics |
3535

3636
Additional conventions not covered by rule files:
3737

README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -271,27 +271,27 @@ transport.close();
271271

272272
## Package exports
273273

274-
| Export path | Purpose | Peer dependencies |
275-
| ----------------------------------------- | ------------------------------------------- | --------------------- |
274+
| Export path | Purpose | Peer dependencies |
275+
| --------------------------------- | ------------------------------------------- | --------------------- |
276276
| `@ably/ai-transport` | Core transport, codec interfaces, utilities | `ably` |
277277
| `@ably/ai-transport/react` | React hooks for any codec | `ably`, `react` |
278278
| `@ably/ai-transport/vercel` | Vercel AI SDK codec, transport factories | `ably`, `ai` |
279279
| `@ably/ai-transport/vercel/react` | React hooks for Vercel's `useChat` | `ably`, `ai`, `react` |
280280

281281
### React hooks
282282

283-
| Hook | Entry point | Description |
284-
| --------------------- | --------------- | --------------------------------------------------- |
285-
| `useClientTransport` | `/react` | Create and memoize a client transport instance |
286-
| `useView` | `/react` | Subscribe to messages with history loading |
287-
| `useSend` | `/react` | Stable send callback |
288-
| `useRegenerate` | `/react` | Regenerate a message (fork the conversation) |
289-
| `useEdit` | `/react` | Edit a message and regenerate from that point |
290-
| `useActiveTurns` | `/react` | Track active turns by client ID |
291-
| `useTree` | `/react` | Navigate branches in a forked conversation |
292-
| `useAblyMessages` | `/react` | Access raw Ably messages |
293-
| `useChatTransport` | `/vercel/react` | Wrap transport for Vercel's `useChat` |
294-
| `useMessageSync` | `/vercel/react` | Sync transport state with `useChat`'s `setMessages` |
283+
| Hook | Entry point | Description |
284+
| -------------------- | --------------- | --------------------------------------------------- |
285+
| `useClientTransport` | `/react` | Create and memoize a client transport instance |
286+
| `useView` | `/react` | Subscribe to messages with history loading |
287+
| `useSend` | `/react` | Stable send callback |
288+
| `useRegenerate` | `/react` | Regenerate a message (fork the conversation) |
289+
| `useEdit` | `/react` | Edit a message and regenerate from that point |
290+
| `useActiveTurns` | `/react` | Track active turns by client ID |
291+
| `useTree` | `/react` | Navigate branches in a forked conversation |
292+
| `useAblyMessages` | `/react` | Access raw Ably messages |
293+
| `useChatTransport` | `/vercel/react` | Wrap transport for Vercel's `useChat` |
294+
| `useMessageSync` | `/vercel/react` | Sync transport state with `useChat`'s `setMessages` |
295295

296296
---
297297

@@ -355,7 +355,7 @@ await view.loadOlder(50);
355355

356356
```typescript
357357
transport.view.on('update', () => {
358-
console.log(transport.view.flattenNodes().map(n => n.message));
358+
console.log(transport.view.flattenNodes().map((n) => n.message));
359359
});
360360

361361
transport.tree.on('turn', (event) => {
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
{
22
"root": true,
3-
"extends": [
4-
"next/core-web-vitals",
5-
"next/typescript"
6-
]
3+
"extends": ["next/core-web-vitals", "next/typescript"]
74
}

demo/vercel/react/use-chat/next.config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import type { NextConfig } from "next";
1+
import type { NextConfig } from 'next';
22

33
const nextConfig: NextConfig = {
4-
serverExternalPackages: ["jsonwebtoken", "ably"],
4+
serverExternalPackages: ['jsonwebtoken', 'ably'],
55
webpack: (config) => {
66
// @ably/ai-transport source uses .js extensions in imports (standard TS ESM convention).
77
// When the library is linked as source (file:../../), webpack needs to resolve
88
// .js imports to .ts files.
99
config.resolve.extensionAlias = {
10-
".js": [".ts", ".tsx", ".js"],
10+
'.js': ['.ts', '.tsx', '.js'],
1111
};
1212
return config;
1313
},

demo/vercel/react/use-chat/postcss.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @type {import('postcss-load-config').Config} */
22
const config = {
33
plugins: {
4-
"@tailwindcss/postcss": {},
4+
'@tailwindcss/postcss': {},
55
},
66
};
77

0 commit comments

Comments
 (0)