Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
afd7322
Stabilize biome+mjs transition baseline
lovasoa Apr 14, 2026
dc36426
Restore the typecheck gate
lovasoa Apr 14, 2026
5ef53f2
Add benchmark profiling workflow
lovasoa Apr 14, 2026
92408d8
Boot the pages through module entrypoints
lovasoa Apr 14, 2026
b194061
Remove abandoned mjs transition artifacts
lovasoa Apr 14, 2026
97df044
Add Biome alongside Prettier
lovasoa Apr 14, 2026
30c0f74
Replace Prettier with Biome
lovasoa Apr 14, 2026
70a9665
Parallelize board tool boot
lovasoa Apr 14, 2026
7fbe2d7
Unwrap board page state module
lovasoa Apr 14, 2026
80656fd
Unwrap board message replay module
lovasoa Apr 14, 2026
9757592
Unwrap board transport module
lovasoa Apr 14, 2026
fe6989f
Align telemetry attributes with OTel semconv
lovasoa Apr 14, 2026
824d044
Unwrap shared module metadata helpers
lovasoa Apr 14, 2026
312a207
Standardize HTTP metrics
lovasoa Apr 14, 2026
de46ace
Unwrap message common module
lovasoa Apr 14, 2026
14463cd
Normalize custom metric names
lovasoa Apr 14, 2026
4dd3a42
Unwrap rate limit common module
lovasoa Apr 14, 2026
c1ea484
Add socket and abuse metrics
lovasoa Apr 14, 2026
2b20168
Unwrap minitpl module
lovasoa Apr 14, 2026
6f0e9b7
Use official OTel runtime metrics package
lovasoa Apr 14, 2026
d215b53
Import minitpl explicitly
lovasoa Apr 14, 2026
7d96c58
Convert shared message modules to ESM
lovasoa Apr 14, 2026
2c52163
Import shared runtime modules explicitly
lovasoa Apr 14, 2026
1317e24
Use explicit imports in tool modules and export intersect helpers
lovasoa Apr 14, 2026
6f1a518
Remove global intersect helpers from intersect module
lovasoa Apr 14, 2026
c66a4da
Drop obsolete harness globals after explicit tool imports
lovasoa Apr 14, 2026
229eafd
Load board tools in parallel after bootstrap
lovasoa Apr 14, 2026
81c8a79
Parallelize index page bootstrap imports
lovasoa Apr 14, 2026
2b29318
Use direct import for shared message common
lovasoa Apr 14, 2026
d7946f2
Simplify board bootstrap import graph
lovasoa Apr 14, 2026
ebac787
Add standard image and OTel version metadata
lovasoa Apr 14, 2026
b0a21ce
fix: resolve TypeScript checkjs failures after mjs migration
lovasoa Apr 14, 2026
61fe2d8
style: satisfy biome array formatting in board bootstrap
lovasoa Apr 14, 2026
cf5e688
chore: mark client-data as explicit ESM package
lovasoa Apr 14, 2026
bc18497
chore(client): remove legacy board_page_state globals
lovasoa Apr 14, 2026
4e109cb
chore(client): remove board_transport globals
lovasoa Apr 14, 2026
03adc6c
chore(client): remove remaining message/rate globals
lovasoa Apr 14, 2026
5b8b58e
chore(client): remove tool metadata globals
lovasoa Apr 14, 2026
d8379d3
refactor(tool): export clear tool registration function
lovasoa Apr 14, 2026
a6a1abf
refactor(tool): export download tool registration function
lovasoa Apr 14, 2026
cd5de46
refactor(tool): export cursor tool registration function
lovasoa Apr 14, 2026
c83bb8a
refactor(tool): export line tool registration function
lovasoa Apr 14, 2026
7d4a552
chore(types): remove message common window globals
lovasoa Apr 14, 2026
e7f8e6d
refactor(tool): export grid tool registration function
lovasoa Apr 14, 2026
57aad1e
refactor(tool): export zoom tool registration function
lovasoa Apr 14, 2026
ab1a43c
refactor(tool): export eraser tool registration function
lovasoa Apr 14, 2026
4968a1c
test: load replay tools via cache-busted dynamic imports
lovasoa Apr 14, 2026
ba29b11
test(playwright): avoid MessageCommon globals in zoom threshold helper
lovasoa Apr 14, 2026
6e290d0
refactor(tool): export remaining drawing tool registrars
lovasoa Apr 15, 2026
0a24be3
refactor(client): register tool modules explicitly at boot
lovasoa Apr 15, 2026
87cb6da
refactor(client): remove remaining runtime boot globals
lovasoa Apr 15, 2026
1043ec5
style(client): clear remaining biome diagnostics
lovasoa Apr 15, 2026
e0abd1c
build(tooling): enforce biome in test and staged hooks
lovasoa Apr 15, 2026
0c2e8aa
refactor(client): modernize shared helpers and simple tools
lovasoa Apr 15, 2026
9df1595
refactor(client): modernize remaining leaf modules
lovasoa Apr 15, 2026
61d0e1a
refactor(server): modernize policy and template helpers
lovasoa Apr 15, 2026
e6107c3
refactor(server): modernize auth and config helpers
lovasoa Apr 15, 2026
55250bb
refactor(server): modernize board persistence internals
lovasoa Apr 15, 2026
965b2af
refactor(server): modernize socket helper layer
lovasoa Apr 15, 2026
e5b94a7
refactor(server): modernize socket event handlers
lovasoa Apr 15, 2026
635e547
refactor(server): migrate configuration helpers to esm
lovasoa Apr 15, 2026
b8ac16c
refactor(server): migrate templating and svg helpers to esm
lovasoa Apr 15, 2026
1c094a1
refactor(client): modernize board state and draw tool internals
lovasoa Apr 15, 2026
daea40e
build(tooling): allow ignored staged files in biome hook
lovasoa Apr 15, 2026
9af01a1
refactor(client): modernize board runtime bindings
lovasoa Apr 15, 2026
e271c60
refactor(client): modernize path data polyfill bindings
lovasoa Apr 15, 2026
f387cc0
refactor(client): modernize board rate limit helpers
lovasoa Apr 15, 2026
62e55e7
refactor(client): modernize board presence helpers
lovasoa Apr 15, 2026
0f2c49f
refactor(server): modernize logfmt and tooling helpers
lovasoa Apr 15, 2026
d58bf0b
refactor(tooling): modernize profiling and test console helpers
lovasoa Apr 15, 2026
5704185
refactor(server): migrate output directory check to esm
lovasoa Apr 15, 2026
3a0a948
refactor(server): migrate output directory check to esm
lovasoa Apr 15, 2026
999bd83
Simplify Dokku deploy metadata wiring
lovasoa Apr 15, 2026
de587ed
Simplify metric dimensions
lovasoa Apr 15, 2026
06faa1b
refactor(client): clean remaining legacy ui helpers
lovasoa Apr 15, 2026
8aa3a30
fix(client): keep text replay compatible with tests
lovasoa Apr 15, 2026
4be4253
refactor(tooling): migrate profile benchmark runner to esm
lovasoa Apr 15, 2026
0aa80cd
Remove redundant metrics
lovasoa Apr 15, 2026
80940dd
refactor(server): migrate message validation to esm
lovasoa Apr 15, 2026
d1ba0e3
refactor(server): migrate auth helpers to esm
lovasoa Apr 15, 2026
802ffe8
refactor(server): enrich board metrics context
lovasoa Apr 15, 2026
dc2106f
refactor(server): include board context in policy metrics
lovasoa Apr 15, 2026
2d643dc
refactor(server): introduce esm configuration module
lovasoa Apr 15, 2026
86cba57
refactor(server): point esm consumers at configuration module
lovasoa Apr 15, 2026
8a17af5
refactor(server): migrate socket policy to esm
lovasoa Apr 15, 2026
cd58e4e
refactor(client): clean remaining board runtime idioms
lovasoa Apr 15, 2026
fd63d39
Clarify metric descriptions
lovasoa Apr 15, 2026
ddc4ca6
refactor(server): migrate board data to esm
lovasoa Apr 15, 2026
e2e9639
refactor(server): migrate runtime bootstrap and sockets to esm
lovasoa Apr 15, 2026
778eb50
refactor(tooling): migrate helper scripts to esm
lovasoa Apr 15, 2026
d839cbe
refactor(server): add esm logfmt bridge
lovasoa Apr 15, 2026
a4595ff
refactor(server): add esm observability bridge
lovasoa Apr 15, 2026
abb59dd
test(server): load observability through esm path
lovasoa Apr 15, 2026
e42b93a
refactor(server): migrate observability to esm
lovasoa Apr 15, 2026
23e5762
refactor(server): migrate logfmt to esm
lovasoa Apr 15, 2026
ae5a966
refactor(server): remove configuration shim
lovasoa Apr 15, 2026
9734c88
refactor(server): import configuration from esm modules
lovasoa Apr 15, 2026
f406533
refactor(scripts): migrate benchmark runner to esm
lovasoa Apr 15, 2026
5f4916a
refactor(server): remove legacy bootstrap shim
lovasoa Apr 15, 2026
cc9f5f1
build(tooling): typecheck migrated benchmark script
lovasoa Apr 15, 2026
aef52f9
fix(client): preserve board state across reconnect
lovasoa Apr 15, 2026
7797993
fix(web): tighten dev cache invalidation
lovasoa Apr 15, 2026
95ee82c
test(web): cover cache headers and asset versioning
lovasoa Apr 15, 2026
8cf76f3
refactor(server): use native esm imports for package metadata
lovasoa Apr 15, 2026
079cee1
fix(socket): reconnect reported users after transport close
lovasoa Apr 15, 2026
ddfce5e
build(web): bump asset version to 1.30.0
lovasoa Apr 15, 2026
fad8e78
style(server): modernize small helper patterns
lovasoa Apr 15, 2026
b56da99
style(server): replace legacy object merging patterns
lovasoa Apr 15, 2026
50d9e98
style(server): remove final legacy object merge
lovasoa Apr 15, 2026
1b12d17
Refactor board boot around server-rendered tools
lovasoa Apr 15, 2026
b487a8c
Convert utility tools to instance-based classes
lovasoa Apr 15, 2026
56ce8b4
Convert basic drawing tools to instance-based classes
lovasoa Apr 15, 2026
a0040e5
refactor(client): remove dead shared module resolver
lovasoa Apr 15, 2026
5b70838
build(tooling): trim biome config to non-default policy
lovasoa Apr 15, 2026
8793681
Convert text and cursor tools to instance-based classes
lovasoa Apr 15, 2026
860bfbc
build(tooling): deduplicate typecheck tsconfig options
lovasoa Apr 15, 2026
ae1442a
Convert pencil tool to an instance-based class
lovasoa Apr 15, 2026
40a9393
Convert hand tool to an instance-based class
lovasoa Apr 15, 2026
10843b6
Finish instance-based tool migration
lovasoa Apr 15, 2026
b226ea7
Remove legacy tool registration workflow
lovasoa Apr 15, 2026
60ad8bd
refactor(client): remove redundant index preload
lovasoa Apr 15, 2026
348f31d
refactor(client): flatten board page state exports
lovasoa Apr 15, 2026
bdd32e0
refactor(client): let board runtime load tool modules
lovasoa Apr 15, 2026
ac6ee11
refactor(client): normalize tool asset paths
lovasoa Apr 15, 2026
19eefa7
Add slow-start board stabilization regression test
lovasoa Apr 15, 2026
6194931
test(playwright): await pencil boot in drawing helper
lovasoa Apr 15, 2026
699b22d
Start board socket connection before tool boot completes
lovasoa Apr 15, 2026
f3bac9f
Handle missing preview boards and clean up OTel log correlation
lovasoa Apr 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
with:
fetch-depth: 0
- name: Dokku
uses: dokku/github-action@036bf9c07f1b40707a16201bc7a349074c3ec554
uses: dokku/github-action@v1.9.0
with:
git_remote_url: ssh://dokku@${{ secrets.SSH_SERVER }}:22/wbo
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.env
node_modules

lib-cov
Expand Down Expand Up @@ -27,3 +28,4 @@ tests_output/
playwright-report/
test-results/
.codex
.profiles/
10 changes: 0 additions & 10 deletions .prettierignore

This file was deleted.

12 changes: 0 additions & 12 deletions .prettierrc

This file was deleted.

38 changes: 25 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@

## architecture

- Process boot + routes + socket server: [server startup](./server/server.js).
- Realtime event handlers + broadcast path: [socket handlers](./server/sockets.js).
- Socket auth, rate-limit enforcement, payload admission: [socket policy](./server/socket_policy.js).
- Canonical inbound payload normalization: [message schema gate](./server/message_validation.js).
- In-memory board model + apply rules + disk sync: [board state engine](./server/boardData.js).
- Page shell that loads runtime bundles: [board document](./client-data/board.html).
- Client state machine + send/receive plumbing: [board runtime](./client-data/js/board.js).
- Process boot + routes + socket server: [server startup](./server/server.mjs).
- HTML templating + client config payload: [templating](./server/templating.mjs), [client config](./server/client_configuration.mjs).
- Shared toolbar catalog + versioned tool asset helpers: [tool catalog](./client-data/js/tool_catalog.js), [tool assets](./client-data/js/tool_assets.js).
- Realtime event handlers + broadcast path: [socket handlers](./server/sockets.mjs).
- Socket auth, rate-limit enforcement, payload admission: [socket policy](./server/socket_policy.mjs).
- Canonical inbound payload normalization: [message schema gate](./server/message_validation.mjs).
- In-memory board model + apply rules + disk sync: [board state engine](./server/boardData.mjs).
- Page shell that server-renders the toolbar and loads the module entrypoint for the board runtime: [board document](./client-data/board.html), [board module boot](./client-data/js/board_main.js).
- Client state machine + staged tool boot + send/receive plumbing: [board runtime](./client-data/js/board.js).
- Shared socket transport utilities: [transport helpers](./client-data/js/board_transport.js).
- Shared geometry/id/color/text clamps: [message primitives](./client-data/js/message_common.js).
- Tool implementations that mutate SVG/DOM: [tool modules](./client-data/tools/).
- Tool modules now default-export a tool class for dynamic `import()` boot, while legacy named `register*Tool` exports may still exist during migration.

## message lifecycle

Expand All @@ -33,34 +36,43 @@

## where to look by concern

- Config/env behavior: [server configuration](./server/configuration.js).
- Config/env behavior: [server configuration](./server/configuration.mjs).
- Browser integration coverage: [playwright specs](./playwright/tests).
- Node behavior coverage: [rate-limit tests](./test-node/rate_limits.test.js).
- Browser runner setup: [playwright config](./playwright.config.ts).
- Server-rendered toolbar/icon/cache coverage: [server route tests](./test-node/server_routes.test.js).

## test commands

- Node suite: `node --test test-node/*.test.js`.
- Browser suite: `npx playwright test playwright/tests/<file>.spec.ts`.
- Throughput check: `npm run bench` before/after suspected performance changes.
- Full gate: `npm test` (Node tests, Playwright, `prettier-check`).
- Auto-format: `npm run prettier` (rules: [prettierrc](./.prettierrc), ignores: [prettierignore](./.prettierignore)).
- CPU + memory profile: `npm run profile` writes `.profiles/benchmark-server.cpuprofile` and `.profiles/benchmark-server.heapprofile`.
- Full gate: `npm test` (Node tests, Playwright, Biome `lint` with warnings treated as failures).
- Auto-format: `npm run format` (Biome `--write --unsafe`).

## notes

- `npm test` needs Chromium (`npx playwright install chromium` when missing).
- `npm test` requires local networking and browser process startup.
- In Playwright specs, assert authoritative socket/app state; avoid sleep-based timing.

## profiling

- Run `npm run profile` to profile `scripts/benchmark-server.mjs`; `.profiles/` is gitignored and keeps local CPU and heap output together.
- CPU: open `.profiles/benchmark-server.cpuprofile` in DevTools Performance and look for hot frames with high self time or repeated stacks in `BoardData.load`, `BoardData.save`, `renderBoardToSVG`, `JSON.parse`, and `JSON.stringify`.
- Memory: open `.profiles/benchmark-server.heapprofile` in DevTools Memory and look for large sampled allocations that survive GC, especially duplicated board objects, large `_children` arrays, and serialization strings.

## formatting

- CI has no separate linter; `npm run prettier-check` + `npm test` define pass/fail.
- `npm run lint` runs the full Biome formatter+linter gate and fails on warnings.
- `npm run format` applies Biome safe and unsafe autofixes.
- Keep edits minimal and style-consistent unless doing full-module refactors.

## change strategy

- Message shape changes: update [server schema gate](./server/message_validation.js) and [shared message primitives](./client-data/js/message_common.js); rerun Node tests.
- Persistence/replay changes: review [board state engine](./server/boardData.js); rerun `node --test test-node/rate_limits.test.js` and `npm test`.
- Message shape changes: update [server schema gate](./server/message_validation.mjs) and [shared message primitives](./client-data/js/message_common.js); rerun Node tests.
- Persistence/replay changes: review [board state engine](./server/boardData.mjs); rerun `node --test test-node/rate_limits.test.js` and `npm test`.
- Tool UX changes: start in [tool modules](./client-data/tools/); verify with Playwright.

## required upkeep
Expand Down
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
FROM node:24-alpine

LABEL org.opencontainers.image.source="https://github.com/lovasoa/whitebophir"

WORKDIR /opt/app

RUN chown -R 1000:1000 /opt/app
Expand All @@ -19,4 +21,4 @@ EXPOSE 80

VOLUME /opt/app/server-data

CMD ["/usr/local/bin/node", "server/server.js"]
CMD ["/usr/local/bin/node", "server/server.mjs"]
36 changes: 36 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.12/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": ["**", "!**/*.html", "!client-data/js/path-data-polyfill.js"]
},
"formatter": {
"indentStyle": "space",
"lineEnding": "lf"
},
"linter": {
"rules": {
"recommended": true,
"style": {
"noDescendingSpecificity": "off"
},
"suspicious": {
"noExplicitAny": "off",
"useIterableCallbackReturn": "off"
},
"correctness": {
"noUnusedVariables": "off",
"noUnusedFunctionParameters": "off",
"noUnusedImports": "off"
},
"complexity": {
"useLiteralKeys": "off",
"useOptionalChain": "off"
}
}
}
}
4 changes: 1 addition & 3 deletions client-data/board.css
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,6 @@ svg {
position: relative;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
Expand Down Expand Up @@ -520,8 +519,7 @@ circle.opcursor {
transition: 0s;
}

/* Internet Explorer specific CSS */
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
@media (forced-colors: active) {
#chooseColor {
color: transparent;
}
Expand Down
51 changes: 16 additions & 35 deletions client-data/board.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<!DOCTYPE html>
<html lang="{{language}}">
<html lang="{{language}}" data-version="{{version}}"{{#moderator}} data-moderator="true"{{/moderator}}>

<head>
<meta charset="utf-8" />
<title>{{board}} | WBO | {{translations.collaborative_whiteboard}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="../board.css" />
<link rel="stylesheet" type="text/css" href="../board.css?v={{version}}" />
<script src="../socket.io/socket.io.js"></script>
<meta name="description" content="{{translations.tagline}}" />
<meta name="keywords"
content="{{translations.collaborative_whiteboard}},online,draw,paint,shared,realtime,wbo,whitebophir" />
<link rel="apple-touch-icon" href="../favicon.svg">
<link rel="icon" type="image/x-icon" sizes="16x16" href="../favicon.ico">
<link rel="apple-touch-icon" href="../favicon.svg?v={{version}}">
<link rel="icon" type="image/x-icon" sizes="16x16" href="../favicon.ico?v={{version}}">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0" />
<meta property="og:title" content="{{board}} board on WBO" />
<meta property="og:url" content="{{baseUrl}}/boards/{{boardUriComponent}}" />
Expand All @@ -20,6 +20,9 @@
{{#languages}}
<link rel="alternate" hreflang="{{.}}" href="{{../boardUriComponent}}?lang={{.}}" />
{{/languages}}
{{#toolStylesheets}}
<link rel="stylesheet" type="text/css" href="{{.}}" />
{{/toolStylesheets}}
</head>

<body>
Expand All @@ -39,11 +42,13 @@
<div id="menu" tabindex="0" {{#hideMenu}}style="display:none;"{{/hideMenu}}>
<div id="menuItems">
<ul id="tools" class="tools">
<li class="tool" tabindex="-1">
<img class="tool-icon" width="35" height="35" src="" alt="icon" />
<span class="tool-name"></span>
<img class="tool-icon secondaryIcon" width="35" height="35" src="data:," alt="icon" />
{{#tools}}
<li class="tool" tabindex="-1" id="toolID-{{name}}" data-tool-name="{{name}}">
<img class="tool-icon" width="35" height="35" src="{{iconUrl}}" alt="{{label}}" />
<span class="tool-name">{{label}}</span>
<img class="tool-icon secondaryIcon" width="35" height="35" src="data:," alt="" />
</li>
{{/tools}}
</ul>

<ul class="tools" id="settings">
Expand All @@ -56,7 +61,7 @@
</li>
<li class="tool" tabindex="-1"
title="{{translations.size}} ({{translations.keyboard_shortcut}}: alt + {{translations.mousewheel}})">
<img class="tool-icon" width="60" height="60" src="icon-size.svg" alt="size" />
<img class="tool-icon" width="60" height="60" src="icon-size.svg?v={{version}}" alt="size" />
<label class="tool-name slider" for="chooseSize">
<span>{{translations.size}}</span>
<input type="range" id="chooseSize" value="4" min="1" max="50" step="1" class="rangeChooser" />
Expand All @@ -81,7 +86,7 @@
</label>
</li>
<li class="tool oneTouch" tabindex="-1" id="connectedUsersToggle">
<img class="tool-icon" width="35" height="35" src="users.svg" alt="" />
<img class="tool-icon" width="35" height="35" src="users.svg?v={{version}}" alt="" />
<span class="tool-name"></span>
</li>
</ul>
Expand All @@ -96,31 +101,7 @@
<script type="application/json" id="translations">{{{ json translations }}}</script>
<script type="application/json" id="configuration">{{{ json configuration }}}</script>
<script type="application/json" id="board-state">{{{ json boardState }}}</script>
<script src="../js/path-data-polyfill.js"></script>
<script src="../js/shared_module_resolver.js?v={{version}}"></script>
<script src="../js/message_tool_metadata.js?v={{version}}"></script>
<script src="../js/message_common.js?v={{version}}"></script>
<script src="../js/rate_limit_common.js?v={{version}}"></script>
<script src="../js/board_page_state.js?v={{version}}"></script>
<script src="../js/board_transport.js?v={{version}}"></script>
<script src="../js/board_message_replay.js?v={{version}}"></script>
<script src="../js/minitpl.js"></script>
<script src="../js/intersect.js"></script>
<script src="../js/board.js?v={{version}}"></script>
<script src="../tools/pencil/wbo_pencil_point.js?v={{version}}"></script>
<script src="../tools/pencil/pencil.js?v={{version}}"></script>
<script src="../tools/cursor/cursor.js?v={{version}}"></script>
<script src="../tools/line/line.js?v={{version}}"></script>
<script src="../tools/rect/rect.js?v={{version}}"></script>
<script src="../tools/ellipse/ellipse.js?v={{version}}"></script>
<script src="../tools/text/text.js?v={{version}}"></script>
<script src="../tools/eraser/eraser.js?v={{version}}"></script>
<script src="../tools/hand/hand.js?v={{version}}"></script>
<script src="../tools/grid/grid.js?v={{version}}"></script>
<script src="../tools/download/download.js?v={{version}}"></script>
<script src="../tools/zoom/zoom.js?v={{version}}"></script>
{{#moderator}}<script src="../tools/clear/clear.js?v={{version}}"></script>{{/moderator}}
<script src="../js/canvascolor.js?v={{version}}"></script>
<script type="module" src="../js/board_main.js?v={{version}}"></script>
</body>

</html>
Loading