Skip to content

Commit

Permalink
feat(web): first implementation of game replay (#166)
Browse files Browse the repository at this point in the history
- fix(web): wrong angle when rotating multiple stacked card.
- fix(web): wrong elevation when dragging a mah-jong tile from hand.
- fix(klondike): some cards are missing symbols.
- feat(server): supports player actions and moves history.
- feat(web): brings a first implementation for game replay.
- feat(klondike): automatically flips first discarded card.
- refactor(server): introduces play action to distinguish draws.
- chore: updates all dependencies.
- chore: patches vitest to fix OOM errors when diffing meshes.
  • Loading branch information
feugy authored Sep 16, 2023
1 parent e4d771a commit 907bcc0
Show file tree
Hide file tree
Showing 204 changed files with 10,481 additions and 3,385 deletions.
15 changes: 13 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# TODO

during replay: host save

## Refactor

- replace windicss with a successor (tailwind or UnoCSS)
- hand: reuse playMeshes() and pickMesh() in handDrag()
- replace windicss with a successor (UnoCSS)
- add tests for web/src/utils/peer-connection
- group candidate target per kind for performance
- all manager managing a collection of behaviors should check their capabilities
Expand All @@ -13,7 +16,7 @@

## UI

- replay
- when hovering target, highlight should have the dragged mesh's shape, not the target shape (what about parts?)
- hand count on peer pointers/player tab?
- score card (Mah-jong, Belote)
- command to reset some mesh state and restart a game (Mah-jong, Belote)
Expand Down Expand Up @@ -92,6 +95,14 @@ The host player is in charge of:

When the host player disconnects, a new host is elected: the first connected player in the game player list becomes host

### Action propagation and cascade

1. `mesh.medatadata.fn()` are only triggered by humans and `moveManager.onMoveObservable`
1. behavior should record action to the Control manager before applying them so they could cascade in order (extreme some rare cases like drawing and snapping)
1. when triggering an action from a notification, behavior must report it with `isLocal` flag to avoid re-cascading
1. `controlManager.apply()` is only applying peers and replay actions: it enrichs their notifications with `isLocal`
1. Replay manager and Game engine ignore notifications marked as local

## Various learnings

Physics engine aren't great: they are all pretty deprecated. [Cannon-es can not be used yet](https://github.com/BabylonJS/Babylon.js/issues/9810).
Expand Down
6 changes: 3 additions & 3 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
},
"dependencies": {
"@tabulous/cli": "workspace:^",
"@urql/core": "^4.1.1",
"@urql/core": "^4.1.2",
"add": "^2.0.6",
"ajv": "^8.12.0",
"arg": "^5.0.2",
"chalk": "^5.3.0",
"chalk-template": "^1.1.0",
"dotenv": "^16.3.1",
"es-main": "^1.2.0",
"es-main": "^1.3.0",
"fast-jwt": "^3.2.0",
"graphql": "^16.8.0",
"lodash.camelcase": "^4.3.0",
"lodash.kebabcase": "^4.1.1",
"undici": "^5.23.0"
"undici": "^5.24.0"
},
"devDependencies": {
"strip-ansi": "^7.1.0"
Expand Down
Binary file modified apps/games/assets/textures/fr/clubs-1.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-1.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-10.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-10.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-11.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-11.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-12.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-12.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-13.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-13.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-2.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-2.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-3.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-3.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-4.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-4.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-5.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-5.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-6.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-6.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-7.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-7.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-8.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-8.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-9.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/clubs-9.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-1.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-1.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-10.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-10.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-11.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-11.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-12.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-12.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-13.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-13.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-2.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-2.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-3.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-3.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-4.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-4.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-5.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-5.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-6.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-6.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-7.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-7.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-8.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-8.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-9.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/diamonds-9.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-1.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-1.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-10.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-10.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-11.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-11.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-12.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-12.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-13.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-13.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-2.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-2.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-3.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-3.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-4.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-4.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-5.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-5.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-6.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-6.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-7.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-7.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-8.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-8.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-9.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/hearts-9.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-1.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-1.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-10.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-10.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-11.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-11.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-12.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-12.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-13.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-13.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-2.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-2.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-3.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-3.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-4.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-4.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-5.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-5.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-6.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-6.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-7.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-7.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-8.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-8.1.ktx2
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-9.1.gl1.webp
Binary file not shown.
Binary file modified apps/games/assets/textures/fr/spades-9.1.ktx2
Binary file not shown.
2 changes: 2 additions & 0 deletions apps/games/chess/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exports[`chess game descriptor > askForParameters() + addPlayer() > enrolls each
"created": 1691859511667,
"guestIds": [],
"hands": [],
"history": [],
"id": "game-unique-id",
"kind": "chess",
"locales": {
Expand Down Expand Up @@ -1111,6 +1112,7 @@ exports[`chess game descriptor > askForParameters() + addPlayer() > enrolls each
"created": 1691859511667,
"guestIds": [],
"hands": [],
"history": [],
"id": "game-unique-id",
"kind": "chess",
"locales": {
Expand Down
2 changes: 2 additions & 0 deletions apps/games/draughts/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exports[`draughts game descriptor > askForParameters() + addPlayer() > enrolls e
"created": 1691859511667,
"guestIds": [],
"hands": [],
"history": [],
"id": "game-unique-id",
"kind": "draughts",
"locales": {
Expand Down Expand Up @@ -1215,6 +1216,7 @@ exports[`draughts game descriptor > askForParameters() + addPlayer() > enrolls e
"created": 1691859511667,
"guestIds": [],
"hands": [],
"history": [],
"id": "game-unique-id",
"kind": "draughts",
"locales": {
Expand Down
3 changes: 3 additions & 0 deletions apps/games/klondike/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exports[`klondike game descriptor > askForParameters() + addPlayer() > enrolls e
"created": 1691859511667,
"guestIds": [],
"hands": [],
"history": [],
"id": "game-unique-id",
"kind": "klondike",
"locales": {
Expand Down Expand Up @@ -2120,6 +2121,7 @@ exports[`klondike game descriptor > askForParameters() + addPlayer() > enrolls e
},
{
"depth": 4.25,
"flip": false,
"height": 0.01,
"id": "discard",
"width": 3,
Expand Down Expand Up @@ -4276,6 +4278,7 @@ exports[`klondike game descriptor > build() > builds game setup 1`] = `
},
{
"depth": 4.25,
"flip": false,
"height": 0.01,
"id": "discard",
"width": 3,
Expand Down
2 changes: 1 addition & 1 deletion apps/games/klondike/logic/builders/board.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function buildBoard() {
anchorable: {
anchors: [
{ id: anchorIds.reserve, ...positions.reserve, ...sizes.card },
{ id: 'discard', ...positions.discard, ...sizes.card },
{ id: 'discard', ...positions.discard, ...sizes.card, flip: false },
...buildGoalAnchors(),
...buildColumnAnchors()
]
Expand Down
4 changes: 4 additions & 0 deletions apps/games/mah-jong/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exports[`mah-jong game descriptor > askForParameters() + addPlayer() > enrolls e
"created": 1691859511667,
"guestIds": [],
"hands": [],
"history": [],
"id": "game-unique-id",
"kind": "mah-jong",
"locales": {
Expand Down Expand Up @@ -11116,6 +11117,7 @@ exports[`mah-jong game descriptor > askForParameters() + addPlayer() > enrolls e
"created": 1691859511667,
"guestIds": [],
"hands": [],
"history": [],
"id": "game-unique-id",
"kind": "mah-jong",
"locales": {
Expand Down Expand Up @@ -22418,6 +22420,7 @@ exports[`mah-jong game descriptor > askForParameters() + addPlayer() > enrolls e
"created": 1691859511667,
"guestIds": [],
"hands": [],
"history": [],
"id": "game-unique-id",
"kind": "mah-jong",
"locales": {
Expand Down Expand Up @@ -33927,6 +33930,7 @@ exports[`mah-jong game descriptor > askForParameters() + addPlayer() > enrolls e
"created": 1691859511667,
"guestIds": [],
"hands": [],
"history": [],
"id": "game-unique-id",
"kind": "mah-jong",
"locales": {
Expand Down
12 changes: 6 additions & 6 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,27 @@
},
"dependencies": {
"@fastify/cors": "^8.3.0",
"@fastify/static": "^6.10.2",
"@fastify/static": "^6.11.1",
"@fastify/websocket": "^8.2.0",
"@isaacs/ttlcache": "^1.4.1",
"@orama/orama": "^1.2.1",
"@orama/orama": "^1.2.4",
"ajv": "^8.12.0",
"deepmerge": "^4.3.1",
"dotenv": "^16.3.1",
"fast-jwt": "^3.2.0",
"fastify": "^4.21.0",
"fastify": "^4.23.2",
"fastify-plugin": "^4.5.1",
"ioredis": "^5.3.2",
"mercurius": "^13.1.0",
"mqemitter-redis": "^5.0.0",
"pino": "^8.15.0",
"pino": "^8.15.1",
"pino-pretty": "^10.2.0",
"rxjs": "^7.8.1"
},
"devDependencies": {
"nodemon": "^3.0.1",
"undici": "^5.23.0",
"ws": "^8.13.0"
"undici": "^5.24.0",
"ws": "^8.14.1"
},
"files": [
"src/",
Expand Down
18 changes: 15 additions & 3 deletions apps/server/src/graphql/games-resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* @typedef {import('.').InviteArgs} InviteArgs
* @typedef {import('.').JoinGameArgs} JoinGameArgs
* @typedef {import('.').KickArgs} KickArgs
* @typedef {import('.').PlayerAction} PlayerAction
* @typedef {import('.').PlayerMove} PlayerMove
* @typedef {import('.').PromoteGameArgs} PromoteGameArgs
* @typedef {import('.').ReceiveGameUpdatesArgs} ReceiveGameUpdatesArgs
* @typedef {import('.').SaveGameArgs} SaveGameArgs
Expand Down Expand Up @@ -258,8 +260,8 @@ export default {
GameOrParameters: {
/**
* Distinguishes returned Game from GameParameters
* @param {?Game|GameParameters} obj - either a Game or a GameParameters object
* @returns {string} the type of this object.
* @param {?Game|GameParameters} obj - either a Game or a GameParameters object.
* @returns the type of this object.
*/
resolveType(obj) {
return obj && 'schema' in obj ? 'GameParameters' : 'Game'
Expand All @@ -270,8 +272,18 @@ export default {
/**
* Serializer for schema.
* @param {import('../services/games').GameParameters<?>} obj - serialized game parameter schema
* @returns {string}
*/
schemaString: obj => JSON.stringify(obj.schema)
},

HistoryRecord: {
/**
* Distinguishes returned PlayerMove and PlayerAction
* @param {?PlayerMove|PlayerAction} obj - either a player move or action object.
* @returns the type of this object.
*/
resolveType(obj) {
return obj && 'fn' in obj ? 'PlayerAction' : 'PlayerMove'
}
}
}
41 changes: 41 additions & 0 deletions apps/server/src/graphql/games.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Game {
tableSpec: TableSpec
colors: ColorSpec
actions: ActionSpec
history: [HistoryRecord]
}

type DetailableState {
Expand Down Expand Up @@ -242,6 +243,7 @@ enum ActionName {
flip
flipAll
increment
play
pop
push
random
Expand All @@ -259,12 +261,39 @@ type ActionSpec {
button2: [ActionName]
}

interface HistoryRecord {
time: Float!
meshId: ID!
playerId: ID!
duration: Int
}

type PlayerAction implements HistoryRecord {
time: Float!
meshId: ID!
playerId: ID!
fn: String!
argsStr: String
revertStr: String
duration: Int
}

type PlayerMove implements HistoryRecord {
time: Float!
meshId: ID!
playerId: ID!
pos: [Float]!
prev: [Float]!
duration: Int
}

input GameInput {
id: ID!
meshes: [MeshInput]
messages: [MessageInput]
cameras: [CameraPositionInput]
hands: [HandInput]
history: [HistoryRecordInput]
}

input DetailableStateInput {
Expand Down Expand Up @@ -436,6 +465,18 @@ input HandInput {
meshes: [MeshInput]!
}

input HistoryRecordInput {
time: Float!
meshId: ID!
playerId: ID!
fn: String
argsStr: String
revertStr: String
pos: [Float]
prev: [Float]
duration: Int
}

type GameParameters {
schemaString: String!
error: String
Expand Down
4 changes: 4 additions & 0 deletions apps/server/src/graphql/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export type Game = Pick<
| 'hands'
| 'preferences'
| 'availableSeats'
| 'history'
>
> & { players?: GamePlayer[] }

Expand All @@ -77,11 +78,14 @@ export type {
DrawableState,
FlippableState,
Hand,
HistoryRecord,
InitialTransform,
LockableState,
Mesh,
Message,
MovableState,
PlayerAction,
PlayerMove,
PlayerPreference,
Point,
QuantifiableState,
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/services/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ import { makeLogger } from '../utils/index.js'
*/

/**
* @typedef {'decrement'|'detail'|'draw'|'flip'|'flipAll'|'increment'|'pop'|'push'|'random'|'reorder'|'rotate'|'setFace'|'snap'|'toggleLock'|'unsnap'|'unsnapAll'} ActionName
* @typedef {'decrement'|'detail'|'draw'|'flip'|'flipAll'|'increment'|'play'|'pop'|'push'|'random'|'reorder'|'rotate'|'setFace'|'snap'|'toggleLock'|'unsnap'|'unsnapAll'} ActionName
*/

/**
Expand Down
Loading

2 comments on commit 907bcc0

@vercel
Copy link

@vercel vercel bot commented on 907bcc0 Sep 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

tabulous – ./apps/web

tabulous-git-main-feugy.vercel.app
tabulous-feugy.vercel.app
tabulous.vercel.app
tabulous.fr

@vercel
Copy link

@vercel vercel bot commented on 907bcc0 Sep 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

tabulous-atelier – ./apps/web

tabulous-atelier-git-main-feugy.vercel.app
tabulous-atelier-feugy.vercel.app
tabulous-atelier.vercel.app

Please sign in to comment.