@chessdrive/board 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ChessDrive
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ The chess pieces are Kosal by philatype (https://github.com/philatype/kosal),
26
+ licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/).
27
+ chess.js (https://github.com/jhlywa/chess.js) is bundled under its BSD 2-Clause
28
+ licence, and Svelte (https://svelte.dev) under its MIT licence.
package/README.md CHANGED
@@ -1,3 +1,123 @@
1
- # Temporary Holding Version
1
+ # @chessdrive/board
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **The Board by ChessDrive**: a chess board your agent shows you and drives, and you can move and draw on. One self-contained `<chessdrive-board>` element: no key, no network calls, no external images or fonts. It runs in Claude artifacts, MCP Apps and any local page.
4
+
5
+ ## Load it
6
+
7
+ Pin the exact version, from jsDelivr:
8
+
9
+ ```html
10
+ <script src="https://cdn.jsdelivr.net/npm/@chessdrive/board@0.1.1/dist/board.umd.js"></script>
11
+ <chessdrive-board></chessdrive-board>
12
+ ```
13
+
14
+ That is a playable board. Use jsDelivr, never unpkg: legacy Claude chat artifacts block unpkg. As a module: `import '@chessdrive/board'` (ESM, `dist/board.js`). The UMD build also defines `window.ChessDriveBoard`.
15
+
16
+ **Never put an API key in the page.** Get paid answers, such as engine lines or game search, through the ChessDrive Connector: in a Claude artifact through the viewer's own MCP connection, elsewhere through your agent's own tool calls. Then pass them to `board.set()`.
17
+
18
+ ## Drive it
19
+
20
+ ```js
21
+ const board = document.querySelector('chessdrive-board');
22
+
23
+ board.set({
24
+ fen: 'r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3',
25
+ marks: [{ arrow: 'f1b5', color: 'green' }, { circle: 'e5', color: 'red' }],
26
+ eval: { cp: 35 },
27
+ mode: 'play-white',
28
+ });
29
+
30
+ board.on('move', ({ san, fen }) => {
31
+ // The person moved. Reply with your own move:
32
+ board.play('Nf6');
33
+ });
34
+ ```
35
+
36
+ Or create one: `ChessDriveBoard.create(document.body, { pgn, ply: 12, moves: true })`.
37
+
38
+ ## The State
39
+
40
+ One object describes the whole Board. `board.state` returns it; `board.set(patch)` merges any part of it. Unknown fields are ignored, never an error.
41
+
42
+ | Field | Value | Default |
43
+ |---|---|---|
44
+ | `fen` | The position. With a game, the position at `ply`. | the start |
45
+ | `pgn`, `ply` | A game, and the half-moves into it on view (0 to its length). Set a `pgn` or a `fen`, not both. | |
46
+ | `orientation` | `white` or `black`: the side at the bottom. | `white` |
47
+ | `lastMove` | The move into the position, in UCI (`e2e4`), tinted. A game supplies its own. | `null` |
48
+ | `marks` | Arrows `{ arrow: 'e2e4' }`, circles `{ circle: 'f7' }` and square highlights `{ square: 'd5' }`, each with a `color` (`green`, `red`, `blue`, `yellow`, `orange`) and an `author`. | `[]` |
49
+ | `annotations` | Verdicts on a game's moves: a map from ply to `{ glyph, comment }`, either part optional. `glyph` is `!!` (best), `!` (good), `?!` (inaccuracy), `?` (mistake) or `??` (blunder). See [Verdicts](#verdicts). | none |
50
+ | `eval` | `{ cp: 35 }` (centipawns, White's view) or `{ mate: -3 }`. `null` hides the bar, and leaves its place once it has shown, so the squares don't move. | `null` |
51
+ | `mode` | `view` (locked), `play-white`, `play-black` or `play-both`. | `play-both` |
52
+ | `moves` | `true` shows the move list. | `false` |
53
+ | `players` | `{ white: { name, rating }, black: { name, rating } }`, or `true` for plain rows, with captures and material. | `null` |
54
+ | `size` | `full` (squares up to 560 px, 520 beside the eval bar, with the moves to their side on a wide page), `phone` (360 px, moves below), `embed` (240 px: the board, buttons and link) or `thumb` (160 px: the board alone). | `full` |
55
+ | `outcome` | Read only, and left out while the game goes on: `{ result, reason }`, with `result` `1-0`, `0-1` or `1/2-1/2`. `reason` is `checkmate`, `stalemate` or `insufficient material`, or in a game the person plays against you (`play-white`, `play-black`) `repetition` or `fifty moves`. It comes from the position, so a resignation in a PGN isn't one. `ChessDriveBoard.outcomeText(outcome)` gives the line shown under the board. | |
56
+
57
+ Marks you set are always yours, the agent's: they're drawn outlined and opaque. Setting `marks` replaces yours and leaves the person's alone.
58
+
59
+ ### Verdicts
60
+
61
+ ```js
62
+ board.set({
63
+ pgn: '1. e4 e5 2. Nf3 Nc6 3. Bb5 a6',
64
+ ply: 5,
65
+ moves: true,
66
+ annotations: {
67
+ 5: { glyph: '!', comment: 'The Ruy Lopez.' },
68
+ 6: { glyph: '?!' },
69
+ },
70
+ });
71
+ ```
72
+
73
+ A ply is the half-moves played once the move is made, so `1` is White's first move, the same count as `ply`. The glyph shows after the move in the move list, in a chip of its colour, and on the move's destination square while that ply is on view. The comment shows under the move list while that ply is on view, as plain text; `embed` and `thumb` leave it out. `0` is the start: it has no move to carry a glyph, but its comment shows.
74
+
75
+ Setting `annotations` replaces them, and `{}` clears them. A new game or position starts without them, and a move played from an earlier ply drops those on the moves it replaced. Each bad annotation is ignored and fires `error`. They never change your Marks, and they stay out of the link back and `toFragment`, so links stay short.
76
+
77
+ ## Commands
78
+
79
+ - `set(patch)` merges a partial State. Returns `false`, and fires `error` for each bad value, which is left as it was.
80
+ - `play(move)` animates one legal move from the position on view, in SAN (`Nf3`) or UCI (`g1f3`, `e7e8n`). An illegal move fires `error` and changes nothing.
81
+ - `animate(line)` plays a sequence (`['e4', 'e5', 'Nf3']` or `'1. e4 e5 2. Nf3'`) move by move. A line with an illegal move plays nothing. Resolves `true` when done, `false` when cut short.
82
+ - `clear()` removes your Marks; the person's stay.
83
+
84
+ There is no `undo`: set `ply` or `pgn`.
85
+
86
+ ## Events
87
+
88
+ The person's actions arrive as DOM `CustomEvent`s on the element and through `board.on(name, fn)`, which returns a function that stops listening. Your own commands never echo back, except as `error`.
89
+
90
+ | Event | Detail |
91
+ |---|---|
92
+ | `move` | `{ uci, san, fen, ply }`: the person's move, a promotion included. A premove arrives when it is played. |
93
+ | `marks` | `{ marks }`: the person's Marks, after they drew or cleared one. |
94
+ | `ply` | `{ ply, fen }`: the person stepped through the game. |
95
+ | `flip` | `{ orientation }`: the person flipped the board. |
96
+ | `ready` | `{ state }`: the Board is on the page. `on('ready')` after that is called at once. |
97
+ | `error` | `{ code, message }`: `invalid_state` or `illegal_move`. |
98
+
99
+ The person moves by drag or click-click, with legal-move dots and a promotion column, and premoves while it's your turn in a `play-white` or `play-black` game. In `view` mode with a game, ← → step it with the squares focused; in a play mode they move the keyboard's square cursor there. They draw arrows with a right-drag and circles with a right-click (Shift red, Alt blue, Ctrl or ⌘ yellow), or with the pen button on a touch screen. A click on the board clears their own Marks.
100
+
101
+ ## Attributes
102
+
103
+ For a page with no script of its own: `fen`, `pgn`, `ply`, `orientation`, `last-move`, `mode`, `moves`, `players` and `size`, read as `set()` reads them.
104
+
105
+ ```html
106
+ <chessdrive-board pgn="1. e4 e5 2. Nf3 Nc6" ply="3" moves size="phone"></chessdrive-board>
107
+ ```
108
+
109
+ ## The look and the link back
110
+
111
+ Maple squares, Kosal pieces, corner coordinates. The Board follows the viewer's light or dark setting (`prefers-color-scheme`), not the page's: on a dark setting its panels turn dark and its squares dim, whatever the page's own colours. To keep it light, add the `data-light` attribute: `<chessdrive-board data-light></chessdrive-board>`. The State can't change the look. Under the board, at every size but `thumb`, a "Board by ChessDrive" link opens the same position on platform.chessdrive.io/board. `ChessDriveBoard.toFragment(state)` and `fromFragment(hash)` read and write that address's fragment.
112
+
113
+ ## On the Connector
114
+
115
+ `app/` is the same build as an MCP App, the page behind the Connector's `show_board` tool: `npm run build:app` inlines `dist/board.umd.js` and the view into `api/connector/board.gen.html`. Commit that file: Railway builds `api` from its own folder, and `make contract` fails while it is stale.
116
+
117
+ ## Versions
118
+
119
+ Semver: adding a State field or an event is a minor version; removing or renaming one is a major. Pin the exact version in pages you share.
120
+
121
+ ## Licence
122
+
123
+ MIT. The Kosal pieces are by philatype, CC BY 4.0.
@@ -0,0 +1,116 @@
1
+ // @chessdrive/board's types, published as dist/board.d.ts. src/types.check.ts keeps them the same as the build's own.
2
+
3
+ export type MarkColour = 'green' | 'red' | 'blue' | 'yellow' | 'orange';
4
+ export type MarkAuthor = 'agent' | 'person';
5
+ /** An arrow (`e2e4`), a circle (`f7`) or a square highlight (`d5`), with a colour and an author. */
6
+ export type Mark =
7
+ | { arrow: string; color: MarkColour; author: MarkAuthor }
8
+ | { circle: string; color: MarkColour; author: MarkAuthor }
9
+ | { square: string; color: MarkColour; author: MarkAuthor };
10
+ /** From White's side: centipawns, or moves to mate (negative when Black mates). */
11
+ export type Eval = { cp: number } | { mate: number };
12
+ export type Player = { name?: string; rating?: number };
13
+ export type Players = { white: Player; black: Player };
14
+ export type Mode = 'view' | 'play-white' | 'play-black' | 'play-both';
15
+ export type Size = 'full' | 'phone' | 'embed' | 'thumb';
16
+ export type Orientation = 'white' | 'black';
17
+ export type Glyph = '!!' | '!' | '?!' | '?' | '??';
18
+ /** A verdict on the move that ends a ply: a glyph, a comment, or both. */
19
+ export type Annotation = { glyph?: Glyph; comment?: string };
20
+ /** How the game on view ended, from its position alone: a resignation in a PGN isn't one. */
21
+ export type Outcome = { result: '1-0' | '0-1' | '1/2-1/2'; reason: 'checkmate' | 'stalemate' | 'insufficient material' | 'repetition' | 'fifty moves' };
22
+
23
+ /** The whole Board. set() takes any part of it; unknown fields are ignored. */
24
+ export type State = {
25
+ /** The position on view: the game's at `ply` when there is a game. */
26
+ fen: string;
27
+ /** The game, when there is one: set as a PGN, or moves played from `fen`. */
28
+ pgn?: string;
29
+ /** Half-moves into the game on view, 0 to its length. */
30
+ ply?: number;
31
+ orientation: Orientation;
32
+ /** The move into the position on view, in UCI. */
33
+ lastMove: string | null;
34
+ marks: Mark[];
35
+ /** Verdicts by ply, shown while that ply is on view; left out when there are none. */
36
+ annotations?: Record<number, Annotation>;
37
+ /** null hides the eval bar; once shown it keeps its place, so the squares don't move. */
38
+ eval: Eval | null;
39
+ mode: Mode;
40
+ /** Shows the move list. */
41
+ moves: boolean;
42
+ /** Shows a row for each player, with captures and material; null hides them. */
43
+ players: Players | null;
44
+ size: Size;
45
+ /** Read only: how the game ended, left out while it goes on. */
46
+ outcome?: Outcome;
47
+ };
48
+
49
+ export type BoardEvents = {
50
+ /** The person's move, a promotion included; a premove arrives when it is played. */
51
+ move: { uci: string; san: string; fen: string; ply: number };
52
+ /** The person's Marks, after they drew or cleared one. */
53
+ marks: { marks: Mark[] };
54
+ /** The person stepped to another ply. */
55
+ ply: { ply: number; fen: string };
56
+ /** The person flipped the board. */
57
+ flip: { orientation: Orientation };
58
+ ready: { state: State };
59
+ error: { code: 'invalid_state' | 'illegal_move'; message: string };
60
+ };
61
+ export type BoardEvent = keyof BoardEvents;
62
+
63
+ /** A patch for set(): any State fields, with `players: true` for plain rows, an eval as a number of centipawns,
64
+ * and arrows as { from, to } too. */
65
+ export type StatePatch = Partial<Omit<State, 'marks' | 'players' | 'eval'>> & {
66
+ marks?: (Partial<Mark> | { from: string; to: string; color?: MarkColour })[];
67
+ players?: Partial<Players> | boolean | null;
68
+ eval?: Eval | number | null;
69
+ [unknown: string]: unknown;
70
+ };
71
+
72
+ /** <chessdrive-board>. Its animate(line) replaces Element.animate (Web Animations). */
73
+ export interface ChessDriveBoardElement extends Omit<HTMLElement, 'animate'> {
74
+ /** The whole Board as one State: a copy, so changing it changes nothing. */
75
+ readonly state: State;
76
+ /** True once the Board is on the page; on('ready') after that is called at once. */
77
+ readonly ready: boolean;
78
+ /** Merges a partial State. Unknown fields are ignored; each bad value fires `error`. False if any did. */
79
+ set(patch: StatePatch): boolean;
80
+ /** Plays one legal move (SAN or UCI) from the position on view; an illegal one fires `error` and returns false. */
81
+ play(move: string): boolean;
82
+ /** Plays a line move by move; resolves false when it is illegal or cut short. */
83
+ animate(line: string[] | string): Promise<boolean>;
84
+ /** Removes the agent's Marks; the person's stay. */
85
+ clear(): void;
86
+ /** Listens for one of the Board's events; returns the function that stops listening. */
87
+ on<E extends BoardEvent>(name: E, fn: (detail: BoardEvents[E]) => void): () => void;
88
+ }
89
+ export declare const ChessDriveBoardElement: { prototype: ChessDriveBoardElement; new (): ChessDriveBoardElement };
90
+
91
+ /** A new Board in `target`, at `state`. */
92
+ export declare function create(target: Element, state?: StatePatch): ChessDriveBoardElement;
93
+ /** The fragment, with its `#`, that opens `state` on chessdrive.io/board. */
94
+ export declare function toFragment(state: State): string;
95
+ /** A fragment as a patch for set(). */
96
+ export declare function fromFragment(hash: string): StatePatch;
97
+ /** An outcome in words: "Checkmate. White wins." */
98
+ export declare function outcomeText(outcome: Outcome): string;
99
+ /** The address of `state` on chessdrive.io/board, as the link back gives it. */
100
+ export declare function boardLink(state: State, site: string): string;
101
+ export declare const START_FEN: string;
102
+ /** This build's version. */
103
+ export declare const version: string;
104
+
105
+ declare global {
106
+ interface HTMLElementTagNameMap {
107
+ 'chessdrive-board': ChessDriveBoardElement;
108
+ }
109
+ interface HTMLElementEventMap {
110
+ move: CustomEvent<BoardEvents['move']>;
111
+ marks: CustomEvent<BoardEvents['marks']>;
112
+ ply: CustomEvent<BoardEvents['ply']>;
113
+ flip: CustomEvent<BoardEvents['flip']>;
114
+ ready: CustomEvent<BoardEvents['ready']>;
115
+ }
116
+ }