chesspal 0.3.5 → 0.4.0

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.
Files changed (191) hide show
  1. package/README.md +1 -0
  2. package/dist/cli/cli.js +17 -4
  3. package/dist/cli/command-line.js +22 -12
  4. package/dist/cli/icon.d.ts +2 -0
  5. package/dist/cli/icon.js +11 -1
  6. package/dist/cli/prompt-hook.d.ts +17 -0
  7. package/dist/cli/prompt-hook.js +100 -0
  8. package/dist/commands/analysis/analyse.d.ts +2 -2
  9. package/dist/commands/analysis/analyse.js +10 -16
  10. package/dist/commands/analysis/annotate.d.ts +8 -0
  11. package/dist/commands/analysis/annotate.js +19 -3
  12. package/dist/commands/answers/agents.d.ts +17 -7
  13. package/dist/commands/answers/agents.js +46 -16
  14. package/dist/commands/answers/clock.js +2 -1
  15. package/dist/commands/answers/drawings.js +6 -2
  16. package/dist/commands/answers/index.d.ts +4 -3
  17. package/dist/commands/answers/index.js +4 -3
  18. package/dist/commands/answers/lines.d.ts +5 -3
  19. package/dist/commands/answers/lines.js +17 -9
  20. package/dist/commands/answers/opening-book.d.ts +13 -7
  21. package/dist/commands/answers/opening-book.js +34 -38
  22. package/dist/commands/answers/opening-tags.d.ts +4 -0
  23. package/dist/commands/answers/opening-tags.js +11 -0
  24. package/dist/commands/answers/play-move.d.ts +3 -1
  25. package/dist/commands/answers/play-move.js +9 -6
  26. package/dist/commands/answers/say.js +2 -1
  27. package/dist/commands/answers/summary.d.ts +7 -3
  28. package/dist/commands/answers/summary.js +16 -5
  29. package/dist/commands/answers/view.d.ts +1 -1
  30. package/dist/commands/answers/view.js +2 -2
  31. package/dist/commands/game/ai-move.js +4 -4
  32. package/dist/commands/game/coach.js +7 -6
  33. package/dist/commands/game/end.js +2 -2
  34. package/dist/commands/game/export.d.ts +1 -1
  35. package/dist/commands/game/index.d.ts +1 -1
  36. package/dist/commands/game/index.js +1 -1
  37. package/dist/commands/game/look.d.ts +7 -3
  38. package/dist/commands/game/look.js +9 -4
  39. package/dist/commands/game/new.js +42 -12
  40. package/dist/commands/game/player-move.d.ts +16 -0
  41. package/dist/commands/game/player-move.js +69 -19
  42. package/dist/commands/game/ponder.js +7 -4
  43. package/dist/commands/game/position.d.ts +2 -1
  44. package/dist/commands/game/position.js +9 -4
  45. package/dist/commands/game/status.d.ts +4 -2
  46. package/dist/commands/game/status.js +24 -14
  47. package/dist/commands/index.d.ts +3 -3
  48. package/dist/commands/index.js +3 -3
  49. package/dist/commands/profiles/past-games.d.ts +7 -2
  50. package/dist/commands/profiles/past-games.js +45 -18
  51. package/dist/commands/registry/context.d.ts +2 -0
  52. package/dist/commands/registry/context.js +5 -0
  53. package/dist/commands/registry/index.d.ts +1 -1
  54. package/dist/commands/registry/index.js +1 -1
  55. package/dist/commands/talk/ai-say.d.ts +1 -1
  56. package/dist/commands/talk/ai-say.js +7 -6
  57. package/dist/core/game.d.ts +13 -0
  58. package/dist/core/game.js +29 -3
  59. package/dist/core/index.d.ts +4 -4
  60. package/dist/core/index.js +3 -3
  61. package/dist/core/openings.d.ts +20 -6
  62. package/dist/core/openings.js +45 -22
  63. package/dist/core/pgn.d.ts +4 -0
  64. package/dist/core/pgn.js +35 -6
  65. package/dist/games/analysis.d.ts +7 -4
  66. package/dist/games/analysis.js +5 -3
  67. package/dist/games/archive.d.ts +25 -5
  68. package/dist/games/archive.js +52 -19
  69. package/dist/games/chat-start.d.ts +2 -2
  70. package/dist/games/chat-start.js +6 -1
  71. package/dist/games/current-game.d.ts +2 -2
  72. package/dist/games/current-game.js +3 -2
  73. package/dist/games/index.d.ts +3 -2
  74. package/dist/games/index.js +3 -2
  75. package/dist/games/position-shown.d.ts +24 -0
  76. package/dist/games/position-shown.js +51 -0
  77. package/dist/games/thoughts.d.ts +12 -4
  78. package/dist/games/thoughts.js +27 -10
  79. package/dist/home/index.d.ts +1 -0
  80. package/dist/home/index.js +1 -0
  81. package/dist/home/same-path.d.ts +7 -0
  82. package/dist/home/same-path.js +24 -0
  83. package/dist/host-files/host-files.d.ts +5 -0
  84. package/dist/host-files/host-files.js +21 -7
  85. package/dist/host-files/index.d.ts +1 -1
  86. package/dist/host-files/index.js +1 -1
  87. package/dist/page/app/anchor.js +15 -11
  88. package/dist/page/app/app.js +49 -14
  89. package/dist/page/app/served.js +30 -38
  90. package/dist/page/app/watch.js +97 -0
  91. package/dist/page/avatars/torretta.css +108 -0
  92. package/dist/page/base.css +1 -1
  93. package/dist/page/board/board.css +11 -8
  94. package/dist/page/board/marks.js +2 -2
  95. package/dist/page/board/pointer.js +2 -2
  96. package/dist/page/dialogs/analyses.js +5 -3
  97. package/dist/page/dialogs/dialogs.css +3 -1
  98. package/dist/page/dialogs/rows.js +72 -0
  99. package/dist/page/dialogs/site-games.js +3 -1
  100. package/dist/page/game/column.js +10 -8
  101. package/dist/page/game/game.css +137 -16
  102. package/dist/page/game/move-list.js +7 -5
  103. package/dist/page/game/plies.js +4 -0
  104. package/dist/page/game/review-progress.js +11 -0
  105. package/dist/page/guide/drawings/drawings.css +0 -1
  106. package/dist/page/guide/en.md +2 -2
  107. package/dist/page/guide/it.md +2 -2
  108. package/dist/page/helps/helps.css +5 -5
  109. package/dist/page/helps/helps.js +3 -181
  110. package/dist/page/helps/rows.js +16 -0
  111. package/dist/page/helps/strategy.js +105 -0
  112. package/dist/page/helps/tactics.js +69 -0
  113. package/dist/page/layout/header.js +49 -6
  114. package/dist/page/layout/icon.ico +0 -0
  115. package/dist/page/layout/layout.css +49 -17
  116. package/dist/page/layout/window.js +45 -6
  117. package/dist/page/lead/lead.js +41 -4
  118. package/dist/page/legend/legend.css +4 -16
  119. package/dist/page/legend/legend.js +28 -15
  120. package/dist/page/profile/settings.js +1 -0
  121. package/dist/page/sounds/sounds.js +1 -1
  122. package/dist/page/style.css +1 -0
  123. package/dist/page/table/action.js +42 -0
  124. package/dist/page/table/table.css +57 -39
  125. package/dist/page/table/window.js +67 -67
  126. package/dist/page/talk/talk.css +52 -118
  127. package/dist/page/talk/talk.js +65 -20
  128. package/dist/page/talk/voice.js +92 -21
  129. package/dist/page/text.js +34 -8
  130. package/dist/page/theme.css +13 -0
  131. package/dist/profiles/profiles.js +5 -1
  132. package/dist/server/bell/bell.d.ts +24 -29
  133. package/dist/server/bell/bell.js +79 -133
  134. package/dist/server/bell/index.d.ts +1 -1
  135. package/dist/server/bell/index.js +2 -2
  136. package/dist/server/mcp/mcp.js +49 -7
  137. package/dist/server/page/analyses.js +15 -4
  138. package/dist/server/page/chat.d.ts +5 -1
  139. package/dist/server/page/chat.js +5 -1
  140. package/dist/server/page/files.js +3 -1
  141. package/dist/server/page/game.d.ts +5 -0
  142. package/dist/server/page/game.js +95 -15
  143. package/dist/server/page/http.d.ts +1 -1
  144. package/dist/server/page/page.d.ts +14 -9
  145. package/dist/server/page/page.js +111 -26
  146. package/dist/server/page/table.d.ts +40 -5
  147. package/dist/server/page/table.js +65 -25
  148. package/dist/server/page/update.d.ts +1 -1
  149. package/dist/server/page/update.js +4 -4
  150. package/dist/server/page/windows.d.ts +22 -0
  151. package/dist/server/page/windows.js +45 -0
  152. package/dist/server/ports/index.d.ts +1 -0
  153. package/dist/server/ports/index.js +2 -0
  154. package/dist/server/ports/ports.d.ts +78 -0
  155. package/dist/server/ports/ports.js +222 -0
  156. package/dist/server/table/browser.d.ts +22 -6
  157. package/dist/server/table/browser.js +98 -20
  158. package/dist/server/table/hosts/chats.d.ts +5 -0
  159. package/dist/server/table/hosts/chats.js +11 -4
  160. package/dist/server/table/hosts/hooks.d.ts +6 -0
  161. package/dist/server/table/hosts/hooks.js +38 -0
  162. package/dist/server/table/hosts/hosts.d.ts +13 -5
  163. package/dist/server/table/hosts/hosts.js +16 -10
  164. package/dist/server/table/hosts/index.d.ts +1 -0
  165. package/dist/server/table/hosts/index.js +1 -0
  166. package/dist/server/table/hosts/play.d.ts +7 -3
  167. package/dist/server/table/hosts/play.js +63 -9
  168. package/dist/server/table/icon/index.d.ts +2 -0
  169. package/dist/server/table/icon/index.js +16 -2
  170. package/dist/server/table/index.d.ts +2 -2
  171. package/dist/server/table/index.js +1 -1
  172. package/dist/server/table/launch.d.ts +5 -4
  173. package/dist/server/table/launch.js +18 -5
  174. package/dist/server/table/link.js +4 -1
  175. package/dist/server/table/table.d.ts +7 -0
  176. package/dist/server/table/table.js +40 -12
  177. package/dist/sites/index.d.ts +1 -1
  178. package/dist/sites/index.js +1 -1
  179. package/dist/sites/{chesscom.d.ts → ratings/chesscom.d.ts} +2 -2
  180. package/dist/sites/{chesscom.js → ratings/chesscom.js} +1 -1
  181. package/dist/sites/{choice.d.ts → ratings/choice.d.ts} +1 -1
  182. package/dist/sites/{choice.js → ratings/choice.js} +1 -1
  183. package/dist/sites/ratings/index.d.ts +1 -0
  184. package/dist/sites/ratings/index.js +2 -0
  185. package/dist/sites/{lichess.d.ts → ratings/lichess.d.ts} +2 -2
  186. package/dist/sites/{lichess.js → ratings/lichess.js} +1 -1
  187. package/dist/sites/{sites.d.ts → ratings/ratings.d.ts} +2 -2
  188. package/dist/sites/{sites.js → ratings/ratings.js} +1 -1
  189. package/instructions/opponent.md +1 -1
  190. package/instructions/start.md +1 -1
  191. package/package.json +1 -1
@@ -1,33 +1,38 @@
1
- /** Reads the words aloud in the voice's language, the moves in words, cutting short what was being read. */
2
- export function speak(words, voice) {
1
+ /** Reads the bubbles aloud one after the other in the voice's language, the moves in words, cutting short what was being read. */
2
+ export function speak(readings, voice) {
3
3
  if (typeof speechSynthesis === "undefined") {
4
4
  return;
5
5
  }
6
- const said = spokenMoves(words.replace(/\*\*/g, ""), voice.saidMove).trim();
7
- speechSynthesis.cancel();
8
- clearTimeout(pending);
9
- if (said === "") {
10
- return;
6
+ stop();
7
+ queue = readings.map((reading) => ({ key: reading.key, said: spokenMoves(reading.words.replace(/\*\*/g, ""), voice.saidMove).trim() })).filter((reading) => reading.said !== "");
8
+ language = voice.language;
9
+ readNextSoon();
10
+ }
11
+ /** Stops reading the bubble the key names, when it is the one read, and goes on with the next. */
12
+ export function skip(key) {
13
+ if (current?.key === key) {
14
+ stop();
15
+ readNextSoon();
11
16
  }
12
- // Chrome drops words spoken right after a cancel, and lists its voices late the first time: the words wait a moment.
13
- pending = setTimeout(() => {
14
- const utterance = new SpeechSynthesisUtterance(said);
15
- const reader = readerFor(voice.language);
16
- utterance.lang = reader?.lang ?? voice.language;
17
- if (reader !== undefined) {
18
- utterance.voice = reader;
19
- }
20
- speechSynthesis.resume();
21
- speechSynthesis.speak(utterance);
22
- }, 150);
23
17
  }
24
- /** Stops reading. */
18
+ /** Stops reading, and forgets what was waiting. */
25
19
  export function hush() {
26
20
  if (typeof speechSynthesis !== "undefined") {
27
- clearTimeout(pending);
28
- speechSynthesis.cancel();
21
+ queue = [];
22
+ stop();
29
23
  }
30
24
  }
25
+ /** The key of the bubble being read, from the moment the voice starts it to its end; none while nothing is read. */
26
+ export function reading() {
27
+ return current?.started === true ? current.key : undefined;
28
+ }
29
+ /** Calls the listener whenever the bubble being read changes: what it returns stops it. */
30
+ export function onReading(listener) {
31
+ listeners.add(listener);
32
+ return () => {
33
+ listeners.delete(listener);
34
+ };
35
+ }
31
36
  /** Whether the browser has a voice for the language: without one the words cannot be read. */
32
37
  export function hasVoice(language) {
33
38
  return typeof speechSynthesis !== "undefined" && readerFor(language) !== undefined;
@@ -61,7 +66,73 @@ export function spokenMoves(words, say) {
61
66
  return say({ piece, ...said, takes: takes !== undefined, square, ...promoted, ...checked });
62
67
  });
63
68
  }
69
+ // What waits to be read, what is read and in which language, and who wants to know which bubble is read.
70
+ let queue = [];
71
+ let current;
72
+ let language = "";
64
73
  let pending;
74
+ const listeners = new Set();
75
+ // Chrome drops words spoken right after a cancel, and lists its voices late the first time: the words wait a moment.
76
+ function readNextSoon() {
77
+ pending = setTimeout(readNext, 150);
78
+ }
79
+ function readNext() {
80
+ const next = queue.shift();
81
+ if (next === undefined) {
82
+ return;
83
+ }
84
+ const utterance = new SpeechSynthesisUtterance(next.said);
85
+ const reader = readerFor(language);
86
+ utterance.lang = reader?.lang ?? language;
87
+ if (reader !== undefined) {
88
+ utterance.voice = reader;
89
+ }
90
+ const reading = { key: next.key, utterance, started: false };
91
+ current = reading;
92
+ // A cancel fires end or error in some browsers and nothing in others: only the events of the utterance read now count, and the page drops its own at once.
93
+ utterance.onstart = () => {
94
+ if (current === reading) {
95
+ reading.started = true;
96
+ // The voice may stop with no event at all: a look each second at whether it still speaks, so no mark stays on a bubble nobody reads.
97
+ reading.watch = setInterval(() => {
98
+ if (current === reading && !speechSynthesis.speaking) {
99
+ finished(reading);
100
+ }
101
+ }, 1000);
102
+ tell();
103
+ }
104
+ };
105
+ utterance.onend = () => finished(reading);
106
+ utterance.onerror = () => finished(reading);
107
+ speechSynthesis.resume();
108
+ speechSynthesis.speak(utterance);
109
+ }
110
+ function finished(reading) {
111
+ if (current === reading) {
112
+ drop();
113
+ readNext();
114
+ }
115
+ }
116
+ // Cuts short what is read, keeping what waits.
117
+ function stop() {
118
+ clearTimeout(pending);
119
+ drop();
120
+ speechSynthesis.cancel();
121
+ }
122
+ function drop() {
123
+ const was = current;
124
+ clearInterval(was?.watch);
125
+ current = undefined;
126
+ if (was?.started === true) {
127
+ tell();
128
+ }
129
+ }
130
+ function tell() {
131
+ const key = reading();
132
+ for (const listener of listeners) {
133
+ listener(key);
134
+ }
135
+ }
65
136
  // A move in SAN, with the pieces' letters or figurines, as the AI writes it; the parts named, castling apart.
66
137
  const movePattern = /(?<![\w♔-♟])(?:(?<castles>O-O(?:-O)?|0-0(?:-0)?)|(?:(?<figurine>[♔-♟])[\uFE0E\uFE0F]?|(?<letter>[KQRBN]))?(?<from>[a-h]?[1-8]?)(?<takes>[x×])?(?<square>[a-h][1-8])(?:=?(?<promotion>[QRBN]|[♔-♟]))?[\uFE0E\uFE0F]?)(?<check>\+\+|[+#])?(?![\w♔-♟])/gu;
67
138
  // A piece's letter, or its figurine: White's run ♔♕♖♗♘♙ and Black's ♚♛♜♝♞♟, in SAN's order.
package/dist/page/text.js CHANGED
@@ -30,6 +30,7 @@ const english = {
30
30
  sounds: "Sounds",
31
31
  voice: "Read the AI's words aloud",
32
32
  noVoice: "This browser has no voice for this language: add one in your system's settings",
33
+ skipReading: "Stop reading this aloud",
33
34
  saidMove: (move) => moveInWords(englishMoveWords, move),
34
35
  newGame: "New game",
35
36
  offerDraw: "Offer a draw",
@@ -40,6 +41,7 @@ const english = {
40
41
  declineDrawTitle: "Decline: moving declines it too",
41
42
  resign: "Resign",
42
43
  resignGame: "Resign this game?",
44
+ keepPlaying: "Play on",
43
45
  settings: "Settings",
44
46
  close: "Close",
45
47
  look: "Look",
@@ -175,31 +177,40 @@ const english = {
175
177
  choose: "Choose the chat to play with: it opens in a window of its own, to keep open.",
176
178
  reviewTitle: "Who reviews the game?",
177
179
  reviewChoose: "Choose the chat that reviews the game: it opens in a window of its own, to keep open.",
178
- restartHint: "Choose the model and effort, then press Claude Code or Codex: the chat starts again, clean, in the same window, once it has ended its answer. The game stays as it is.",
180
+ restartHint: "The chat starts again, clean, in the same window, once it has ended its answer, with the chat, model and effort chosen here. The game stays as it is.",
179
181
  byHand: (name) => `You opened this chat yourself: change ${name}'s model with /model in its window, or close it and open one from here, with the choices below.`,
180
182
  noChat: "No chat at the table: choose one",
181
183
  change: "Change the chat or its model",
182
184
  install: (name) => `To play with ${name}, install Claude Code or Codex, then open this page again.`,
185
+ chat: "Chat",
183
186
  model: "Model",
184
187
  effort: "Effort",
185
- asHost: (host) => `as set in ${host}`,
186
- opening: "Opening the chat… In the terminal, if it asks whether you trust the folder, choose Yes with the arrow keys and press Enter.",
188
+ atTable: "At the table",
189
+ act: { open: (host) => `Start ${host}`, review: (host) => `Review with ${host}`, restart: (host) => `Restart ${host}`, switch: (host) => `Switch to ${host}` },
190
+ opening: "Opening the chat… In the terminal, if it asks whether you trust the folder, say yes (in Claude Code, go down to Yes with the arrow keys) and press Enter; with Codex, on the hooks, choose Trust all and continue.",
187
191
  again: (name) => `${name} starts again with a clean chat for the new game…`,
188
192
  restart: "The chat starts again, clean…",
189
193
  waiting: (name) => `${name} starts again once it has ended its answer…`,
190
- restartChanged: "Starts again, clean, with the new choices",
194
+ newGameWaiting: (name) => `The new game starts once ${name} has ended its answer…`,
191
195
  restartNow: "Start again now: it cuts the answer it is giving",
192
196
  cancel: "Do not start again",
197
+ newGameNow: "Start the new game now: it cuts the answer it is giving",
198
+ newGameCancel: "Do not start the new game",
193
199
  failed: "The terminal did not open: type the command below in a terminal.",
194
200
  notRestarted: "The chat did not start again: close its window, then open another from here.",
195
201
  typeIt: "Or type in a terminal:",
196
202
  },
197
203
  commandHere: (name) => `/clear and the other commands go in the window where you opened ${name}, not here.`,
204
+ link: {
205
+ going: "A game is going on: finish it, then open the link again.",
206
+ gone: "The game of this link is not here: it was deleted, or it is another profile's.",
207
+ },
198
208
  refused: {
199
209
  "no-game": () => "There is no game going on: press + to start one, or ask for one in the chat.",
200
210
  "ai-to-move": () => "Wait for the AI's move.",
201
211
  illegal: () => "That move is not legal here.",
202
212
  "clear-chat": (most, name) => english.clearChat(name),
213
+ "new-game-profile": () => "The new game did not start: you changed profile while it waited. Start it again from this one.",
203
214
  "words-empty": () => "Write something first.",
204
215
  "words-spent": () => "You have used up your words for this move: write again after the AI's move.",
205
216
  "words-long": (most) => `Write at most ${most} characters.`,
@@ -210,6 +221,8 @@ const english = {
210
221
  "profile-game": () => english.profile.whilePlaying,
211
222
  "game-going": () => "A game is going on: finish it before you bring another to analyse.",
212
223
  "pgn-unreadable": () => "Chesspal cannot read this PGN: check you copied the whole game, its tags and its moves.",
224
+ "game-missing": () => "This game is no longer there: pick another.",
225
+ "game-not-archived": () => "The game just over is not saved yet: try again in a moment.",
213
226
  "update-game": () => english.update.whilePlaying,
214
227
  "update-chat": (most, name) => `${name} is answering: update once it has done.`,
215
228
  },
@@ -455,6 +468,7 @@ const italian = {
455
468
  sounds: "Suoni",
456
469
  voice: "Leggi ad alta voce le parole dell'AI",
457
470
  noVoice: "Questo browser non ha una voce per questa lingua: aggiungila nelle impostazioni del sistema",
471
+ skipReading: "Smetti di leggere questo a voce",
458
472
  saidMove: (move) => moveInWords(italianMoveWords, move),
459
473
  newGame: "Nuova partita",
460
474
  offerDraw: "Proponi patta",
@@ -465,6 +479,7 @@ const italian = {
465
479
  declineDrawTitle: "Rifiuta: anche muovere rifiuta",
466
480
  resign: "Abbandona",
467
481
  resignGame: "Abbandonare la partita?",
482
+ keepPlaying: "Continua",
468
483
  settings: "Impostazioni",
469
484
  close: "Chiudi",
470
485
  look: "Aspetto",
@@ -600,31 +615,40 @@ const italian = {
600
615
  choose: "Scegli la chat con cui giocare: parte in una finestra a parte, da tenere aperta.",
601
616
  reviewTitle: "Chi analizza la partita?",
602
617
  reviewChoose: "Scegli la chat che analizza la partita: parte in una finestra a parte, da tenere aperta.",
603
- restartHint: "Scegli modello e ragionamento, poi premi Claude Code o Codex: la chat riparte pulita nella stessa finestra, appena ha finito di rispondere. La partita resta com'è.",
618
+ restartHint: "La chat riparte pulita nella stessa finestra, appena ha finito di rispondere, con la chat, il modello e il ragionamento scelti qui. La partita resta com'è.",
604
619
  byHand: (name) => `Questa chat l'hai aperta tu: cambia il modello di ${name} con /model nella sua finestra, oppure chiudila e aprine una da qui, con le scelte qui sotto.`,
605
620
  noChat: "Nessuna chat al tavolo: scegline una",
606
621
  change: "Cambia la chat o il modello",
607
622
  install: (name) => `Per giocare con ${name}, installa Claude Code o Codex, poi apri di nuovo questa pagina.`,
623
+ chat: "Chat",
608
624
  model: "Modello",
609
625
  effort: "Ragionamento",
610
- asHost: (host) => `come è impostato in ${host}`,
611
- opening: "Apro la chat… Nel terminale, se ti chiede se ti fidi della cartella, scegli Yes con le frecce e premi Invio.",
626
+ atTable: "Al tavolo",
627
+ act: { open: (host) => `Avvia ${host}`, review: (host) => `Analizza con ${host}`, restart: (host) => `Riavvia ${host}`, switch: (host) => `Passa a ${host}` },
628
+ opening: "Apro la chat… Nel terminale, se ti chiede se ti fidi della cartella, rispondi sì (in Claude Code scendi su Yes con le frecce) e premi Invio; con Codex, sugli «hook», scegli Trust all and continue.",
612
629
  again: (name) => `${name} riparte con una chat pulita per la nuova partita…`,
613
630
  restart: "La chat riparte pulita…",
614
631
  waiting: (name) => `${name} riparte appena ha finito di rispondere…`,
615
- restartChanged: "Riparte pulita con le nuove scelte",
632
+ newGameWaiting: (name) => `La nuova partita comincia appena ${name} ha finito di rispondere…`,
616
633
  restartNow: "Riparti adesso: interrompe la risposta in corso",
617
634
  cancel: "Non ripartire",
635
+ newGameNow: "Comincia adesso la nuova partita: interrompe la risposta in corso",
636
+ newGameCancel: "Non cominciare la nuova partita",
618
637
  failed: "Il terminale non si è aperto: scrivi il comando qui sotto in un terminale.",
619
638
  notRestarted: "La chat non è ripartita: chiudi la sua finestra, poi aprine un'altra da qui.",
620
639
  typeIt: "Oppure scrivi in un terminale:",
621
640
  },
622
641
  commandHere: (name) => `/clear e gli altri comandi vanno scritti nella finestra dove hai aperto ${name}, non qui.`,
642
+ link: {
643
+ going: "C'è una partita in corso: finiscila, poi riapri il link.",
644
+ gone: "La partita di questo link non c'è: è stata cancellata, o è di un altro profilo.",
645
+ },
623
646
  refused: {
624
647
  "no-game": () => "Nessuna partita in corso: premi + per iniziarne una, o chiedila nella chat.",
625
648
  "ai-to-move": () => "Aspetta la mossa dell'AI.",
626
649
  illegal: () => "Questa mossa qui non è legale.",
627
650
  "clear-chat": (most, name) => italian.clearChat(name),
651
+ "new-game-profile": () => "La nuova partita non è cominciata: hai cambiato profilo mentre aspettava. Ricominciala da questo.",
628
652
  "words-empty": () => "Scrivi prima qualcosa.",
629
653
  "words-spent": () => "Hai finito le parole per questa mossa: scrivi di nuovo dopo quella dell'AI.",
630
654
  "words-long": (most) => `Scrivi al massimo ${most} caratteri.`,
@@ -635,6 +659,8 @@ const italian = {
635
659
  "profile-game": () => italian.profile.whilePlaying,
636
660
  "game-going": () => "C'è una partita in corso: finiscila prima di portarne un'altra da analizzare.",
637
661
  "pgn-unreadable": () => "Chesspal non riesce a leggere questo PGN: controlla di aver copiato la partita intera, intestazioni e mosse.",
662
+ "game-missing": () => "Questa partita non c'è più: scegline un'altra.",
663
+ "game-not-archived": () => "La partita appena finita non è ancora salvata: riprova fra un momento.",
638
664
  "update-game": () => italian.update.whilePlaying,
639
665
  "update-chat": (most, name) => `${name} sta rispondendo: aggiorna quando ha finito.`,
640
666
  },
@@ -62,6 +62,19 @@
62
62
  --badge-mistake: #d4650f;
63
63
  --badge-miss: #c2407d;
64
64
  --badge-blunder: #d22c2c;
65
+ /* The kings' badges on a game over, the same in both themes: the winner's gold crown, the mate in red on white, the flag that fell, the draw */
66
+ --king-crown: #e0a526;
67
+ --king-crown-ink: #3b2a00;
68
+ --king-mate: #c81e1e;
69
+ --king-flag: #4a4f57;
70
+ --king-draw: #69727d;
71
+ /* The two sides as the game's column draws them, in the bar and the chart of who is ahead and in the helps' bars and tiles, the same in both themes:
72
+ White, Black, and both, each half 3:1 at least with the tile's green tick; their edge keeps the white half in sight on a light card and the black one
73
+ on a dark card. The board's map and counts keep their own, darker on the squares (Zoe) */
74
+ --side-white: #f5f5f2;
75
+ --side-black: #33383f;
76
+ --side-both: #8a9099;
77
+ --side-edge: color-mix(in srgb, var(--side-both) 60%, transparent);
65
78
  --shadow: 0 1px 2px rgba(29, 39, 51, 0.06), 0 6px 18px rgba(29, 39, 51, 0.06);
66
79
  /* On a light page the board keeps a thin edge in its dark squares colour, and a deeper shadow, so its light squares do not melt into the page */
67
80
  --board-shadow: 0 0 0 1px color-mix(in oklab, var(--dark-square) 55%, transparent), 0 2px 4px rgba(29, 39, 51, 0.1), 0 10px 28px rgba(29, 39, 51, 0.12);
@@ -23,8 +23,12 @@ export function listProfiles(root) {
23
23
  * together make one: the second finds its folder taken.
24
24
  */
25
25
  export function ensureProfile(root, account) {
26
+ // Every command asks, so the common answer comes first: a profile is there.
27
+ if (profileIds(root).length > 0) {
28
+ return;
29
+ }
26
30
  const id = freeId(root, slugOf(account));
27
- if (profileIds(root).length === 0 && claimed(root, id)) {
31
+ if (claimed(root, id)) {
28
32
  fillProfile(root, id, { name: account }, true);
29
33
  }
30
34
  }
@@ -1,4 +1,4 @@
1
- import { type IncomingMessage, type ServerResponse } from "node:http";
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  /** What the chat hears from the doorbell: one line for whatever the player did, never what, only where to read it. */
3
3
  export declare const bellLine = "The player acted on the board page: call status to see it.";
4
4
  /**
@@ -10,6 +10,7 @@ export declare class Bell {
10
10
  private pending;
11
11
  private busy;
12
12
  private woken;
13
+ private hooked;
13
14
  private readonly news;
14
15
  private readonly changed;
15
16
  /**
@@ -25,38 +26,34 @@ export declare class Bell {
25
26
  get listening(): boolean;
26
27
  /** The chat calls a tool, so it is in a turn: true when nobody was listening before. */
27
28
  chatCalls(): boolean;
28
- /** The player acted on the page: the hook waiting hears it now, or the next one as soon as it comes. */
29
- ring(): void;
30
- /** A hook waits for the bell, so the chat's turn has ended; the hook before it, of a turn already ended, is let go. */
31
- listen(request: IncomingMessage, response: ServerResponse): void;
29
+ /**
30
+ * The player's message, or one the host sent for the doorbell or an agent, starts the chat's turn, as the prompt hook says: the hook of the turn
31
+ * before is let go without a ring, and what the player does on the page meanwhile waits for the turn's end, as during a tool. A chat nobody heard
32
+ * stays unheard until its first tool, which starts the AI's clock, held till then: the message moves no clock.
33
+ */
34
+ chatPrompted(): void;
35
+ /**
36
+ * The player acted on the page: the hook waiting hears it now, or the next one as soon as it comes. In a host that runs no hook nothing tells the end of
37
+ * a turn: an act the chat waits for finds it out of its turn, and nobody hears the player, unless the chat `mayBeAtWork` on the AI's move meanwhile.
38
+ */
39
+ ring(mayBeAtWork?: boolean): void;
40
+ /**
41
+ * A hook waits for the bell, so the chat's turn has ended; the hook before it, of a turn already ended, is let go. A ring no hook heard is dropped when
42
+ * the chat is `told` all of it otherwise: by the player's message that came with the move the prompt hook played.
43
+ */
44
+ listen(request: IncomingMessage, response: ServerResponse, told?: boolean): void;
32
45
  /** The chat left the table (D-104): its process ended, and nobody is in a turn or waits for the bell; a ring no hook heard is no longer news for anyone. */
33
46
  chatLeft(): void;
34
47
  private tellIf;
35
48
  }
36
49
  /**
37
- * Whose page answers at a port, for the hook to find its own: the chat's session, the player's folder, whether a browser has the page open, Chesspal's
38
- * version, and whether the table serves it (D-104), with the session and the folder of the chat attached to it, never its own process's.
50
+ * Notes in the data folder the port a chat's page took off its folder's usual ports, one file per page, for the hook to find it there; then forgets, off
51
+ * the caller's clock, the notes of its folder whose page is gone: nobody listens at the port, or another folder's page does. A page that answers late
52
+ * is busy, not gone, and keeps its note. It throws when the note cannot be written.
39
53
  */
40
- export declare function bellProbe(folder: string, open?: boolean, table?: TableChat): BellProbe;
41
- /** The chat attached to the table, as its probe says it: its session, null for none or for Codex, and its folder; neither while no chat is attached. */
42
- export interface TableChat {
43
- session: string | null;
44
- chat?: string;
45
- }
46
- /** What a page says of itself at `/api/bell?probe`. */
47
- export interface BellProbe {
48
- session: string | null;
49
- folder: string;
50
- open: boolean;
51
- version?: string;
52
- table?: true;
53
- /** The folder of the chat attached to the table. */
54
- chat?: string;
55
- }
56
- /** What the page at a port of this computer says of itself; undefined when none answers there. */
57
- export declare function pageAt(port: number): Promise<BellProbe | undefined>;
58
- /** The ports a folder's page tries first: one in each of a few ranges, by its path, since Windows may reserve a range. The hook looks for it there. */
59
- export declare function pagePorts(folder: string): number[];
54
+ export declare function notePagePort(home: string, folder: string, port: number, wait?: number): Promise<void>;
55
+ /** Forgets the note of a page that closes, unless another folder's page took its port since; a note that cannot go is left for the next page to clear. */
56
+ export declare function forgetPagePort(home: string, folder: string, port: number): void;
60
57
  /** What Codex's Stop hook answers to wake the chat: its turn goes on with the doorbell's line. */
61
58
  export declare const codexBellAnswer: string;
62
59
  /**
@@ -85,5 +82,3 @@ export type BellHeard = {
85
82
  export declare function bellHeard(input: string, ports?: number[]): Promise<BellHeard>;
86
83
  /** Adds a line to the log of the hooks that heard no bell, keeping its last lines; a log that cannot be written never stops the hook. */
87
84
  export declare function noteSilence(folder: string, why: string, now?: Date): void;
88
- /** Whether two paths name the same folder: without regard to case on Windows. */
89
- export declare function samePath(one: string, other: string): boolean;