overplay 4.0.0 → 4.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.1.0 — 2026-10-17
4
+
5
+ - New default shortcut to open and close comments: **Alt+C** (**Option+C** on Mac). It works on any keyboard layout. Alt+Shift+C still works, but on Windows Alt+Shift can switch the keyboard language. Settings shows Option on Mac. Set `keyboardShortcut` to keep another key.
6
+ - The update notice in the sidebar footer now offers a prompt for your coding agent: Show Prompt and Copy Prompt. The prompt updates the package, reads the release notes, clears the bundler cache (Vite, Next.js, Nuxt and others) and restarts the dev server. A version bump alone often kept serving the old widget.
7
+ - The update notice no longer shows for the script tag, which loads the server's copy and updates by itself.
8
+ - Dashboard: on the Projects page each team is a panel with its name, project count and New Project button. Project tiles use the brand blue.
9
+ - Website: the MCP section shows a short chat in which a coding agent reads yesterday's comments, fixes one and replies.
10
+ - The public source moves to [github.com/artemsvit/Overplay-Community](https://github.com/artemsvit/Overplay-Community): the widget and the Community server. Links on the site and in the package point there.
11
+
3
12
  ## 4.0.0 — 2026-10-17
4
13
 
5
14
  The package and code now use the Overplay name everywhere. This release has breaking changes.
package/LICENSE CHANGED
@@ -1,5 +1,8 @@
1
1
  MIT License
2
2
 
3
+ This license covers the Overplay widget (the `overplay` npm package) and every file outside
4
+ `service/`. The service in `service/` uses the Functional Source License: see service/LICENSE.md.
5
+
3
6
  Copyright (c) 2026 Overplay contributors
4
7
 
5
8
  Permission is hereby granted, free of charge, to any person obtaining a copy
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://www.npmjs.com/package/overplay"><img src="https://img.shields.io/npm/v/overplay?color=575ce5" alt="npm version"></a>
13
- <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-575ce5" alt="MIT license"></a>
13
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/widget-MIT-575ce5" alt="Widget: MIT license"></a> <a href="./service/LICENSE.md"><img src="https://img.shields.io/badge/service-FSL--1.1--MIT-575ce5" alt="Service: FSL-1.1-MIT license"></a>
14
14
  <a href="https://overplay.app/"><img src="https://img.shields.io/badge/cloud-overplay.app-575ce5" alt="Cloud"></a>
15
15
  </p>
16
16
 
@@ -110,7 +110,7 @@ Read the [self-hosting guide](docs/SELF_HOSTING.md).
110
110
 
111
111
  | Key | Action |
112
112
  | --- | --- |
113
- | <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd> (<kbd>Option</kbd> on Mac) | Open or close the widget |
113
+ | <kbd>Alt</kbd> + <kbd>C</kbd> (<kbd>Option</kbd> + <kbd>C</kbd> on Mac) | Open or close the widget |
114
114
  | <kbd>Esc</kbd> | Cancel |
115
115
  | <kbd>Enter</kbd> | Place a pin on the focused element |
116
116
 
@@ -132,7 +132,10 @@ The hosted service (Cloudflare Worker + D1) is in [`service/`](service/README.md
132
132
 
133
133
  ## License
134
134
 
135
- [MIT](./LICENSE)
135
+ - **Widget** (the `overplay` npm package: `src/`, `dist/`): [MIT](./LICENSE). Use it on any site, free.
136
+ - **Service** (Cloud and Community: everything in `service/`): [Functional Source License 1.1, MIT Future License](./service/LICENSE.md) (FSL-1.1-MIT). You may run Community for free, for your own company and your clients, and change it. You may not offer it as a competing paid or hosted product. Two years after each release, that release becomes MIT.
137
+
138
+ Code published before this change keeps the MIT license it was published under.
136
139
 
137
140
  ## Connect a coding agent
138
141
 
@@ -421,6 +421,10 @@ function matchesShortcut(event, shortcut) {
421
421
  const code = /^[a-z]$/.test(shortcut.key) ? `Key${shortcut.key.toUpperCase()}` : /^\d$/.test(shortcut.key) ? `Digit${shortcut.key}` : "";
422
422
  return event.key.toLowerCase() === shortcut.key || !!code && event.code === code;
423
423
  }
424
+ function shortcutLabel(value, mac) {
425
+ const names = mac ? { alt: "Option", option: "Option", meta: "Cmd", cmd: "Cmd", ctrl: "Control", control: "Control" } : { alt: "Alt", option: "Alt", meta: "Meta", cmd: "Meta", ctrl: "Ctrl", control: "Ctrl" };
426
+ return value.toLowerCase().split("+").map((part) => part.trim()).filter(Boolean).map((part) => names[part] ?? (part.length === 1 ? part.toUpperCase() : part[0].toUpperCase() + part.slice(1))).join(" + ");
427
+ }
424
428
  function newId() {
425
429
  if (typeof crypto.randomUUID === "function") return crypto.randomUUID();
426
430
  const bytes = crypto.getRandomValues(new Uint8Array(16));
@@ -470,10 +474,26 @@ function newActivity(comments, seen, selfId) {
470
474
  function sameComments(a, b) {
471
475
  return a.length === b.length && JSON.stringify(a) === JSON.stringify(b);
472
476
  }
473
- const version = "4.0.0";
477
+ const version = "4.1.0";
474
478
  const VERSION = version;
475
479
  const PACKAGE = "overplay";
476
- const UPDATE_COMMAND = `npm install ${PACKAGE}@latest`;
480
+ function updatePrompt(current, latest) {
481
+ return `Update the Overplay widget in this project from version ${current} to ${latest}.
482
+
483
+ Steps:
484
+ 1. Find the package manager (look for package-lock.json, pnpm-lock.yaml, yarn.lock or bun.lockb) and update the package "${PACKAGE}" to ${latest}:
485
+ npm install ${PACKAGE}@${latest} (or: pnpm add ${PACKAGE}@${latest}, yarn add ${PACKAGE}@${latest}, bun add ${PACKAGE}@${latest})
486
+ 2. Read the release notes from ${current} to ${latest}: https://github.com/artemsvit/Overplay-Community/blob/main/CHANGELOG.md
487
+ If a release lists breaking changes, update our code to match. Keep serverUrl, projectId and the other options as they are.
488
+ 3. Stop the dev server and clear the bundler cache, so the old widget is not served again:
489
+ - Vite: delete node_modules/.vite
490
+ - Next.js: delete .next
491
+ - Nuxt: delete .nuxt and node_modules/.cache
492
+ - Other tools (webpack, Parcel, Astro, SvelteKit): delete node_modules/.cache, .parcel-cache, .astro or .svelte-kit if they exist.
493
+ 4. Start the dev server again and tell me to hard-reload the browser (Cmd+Shift+R or Ctrl+Shift+R).
494
+ 5. Run the project's type check and build, and fix any errors.
495
+ 6. When you finish, list the files you changed. The update is done when the widget no longer shows "Version ${latest} is available".`;
496
+ }
477
497
  function isNewer(latest, current) {
478
498
  const parse = (v) => {
479
499
  var _a;
@@ -572,6 +592,8 @@ const paths = {
572
592
  arrow: "M12 19V5m-6 6 6-6 6 6",
573
593
  refresh: "M20.5 12a8.5 8.5 0 1 1-2.6-6.1L20.5 8.5M20.5 3.5v5h-5",
574
594
  open: "M7 17 17 7M9 7h8v8",
595
+ copy: "M9 9h11v11H9zM5 15H4V4h11v1",
596
+ sparkle: "M10 3.5c.6 3.9 2.6 5.9 6.5 6.5-3.9.6-5.9 2.6-6.5 6.5-.6-3.9-2.6-5.9-6.5-6.5 3.9-.6 5.9-2.6 6.5-6.5ZM18 14.5c.3 1.9 1.1 2.7 3 3-1.9.3-2.7 1.1-3 3-.3-1.9-1.1-2.7-3-3 1.9-.3 2.7-1.1 3-3Z",
575
597
  trash: "M3 6h18M9 6V4h6v2M5 6l1 14h12l1-14M10 10v6m4-6v6",
576
598
  pin: "M12 21s7-6 7-13A7 7 0 0 0 5 8c0 7 7 13 7 13ZM12 6v4M10 8h4",
577
599
  search: "m21 21-5-5M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Z",
@@ -671,6 +693,10 @@ const tokens = {
671
693
  warning: "#745320",
672
694
  "warning-soft": "#fff8e8",
673
695
  "warning-line": "#ebd59f",
696
+ // AI: a light violet that marks AI features, such as the AI Install prompt. Never for buttons.
697
+ ai: "#6d4fe0",
698
+ "ai-soft": "#f6f3ff",
699
+ "ai-line": "#e3dbff",
674
700
  // Shape. Buttons and chips are pills; cards and windows have soft corners; big page panels have 26px.
675
701
  "radius-sm": "6px",
676
702
  radius: "10px",
@@ -710,6 +736,9 @@ const darkTokens = {
710
736
  warning: "#efc56e",
711
737
  "warning-soft": "#2f2718",
712
738
  "warning-line": "#57472a",
739
+ ai: "#a994ff",
740
+ "ai-soft": "#1d1a2c",
741
+ "ai-line": "#3b3263",
713
742
  "shadow-sm": "0 0 0 1px #ffffff14,0 1px 2px #00000066",
714
743
  shadow: "0 1px 3px #00000066,0 16px 40px -12px #000000b3"
715
744
  };
@@ -801,7 +830,7 @@ const styles = `
801
830
  .anchor-status .quiet{display:inline-flex;align-items:center;gap:6px;margin-top:10px;padding:6px 11px 6px 9px;font-size:13px;font-weight:600}.anchor-status .quiet svg{width:15px;height:15px;flex-shrink:0}
802
831
  @media(max-width:600px){.panel,.panel.left{inset:0;width:auto;border:0}.panel.placing{visibility:hidden}.panel.behind{visibility:hidden}.launcher{right:16px;bottom:16px}.notice,.notice.beside-panel,.notice.beside-panel.left{left:50%;bottom:24px;width:calc(100vw - 32px);font-size:13px}.compose-hint{display:none}.compose-bar{justify-content:flex-end}.toasts,.toasts.left,.toasts.beside-panel,.toasts.beside-panel.left{left:16px;right:16px;width:auto;max-width:none;bottom:82px}.toasts.top,.toasts.beside-panel.top{top:82px;bottom:auto}.toasts.beside-panel{display:none}.head{padding-top:18px}}
803
832
  .popover.moving{visibility:hidden;pointer-events:none}
804
- .update-bar{display:grid;grid-template-columns:30px 1fr auto;align-items:start;gap:4px 12px;padding:14px 12px 16px 20px;background:var(--cw-accent-soft);border-top-color:var(--cw-accent-line)}.update-icon{display:grid;place-items:center;width:30px;height:30px;border-radius:9px;background:var(--cw-surface);color:var(--cw-accent);box-shadow:var(--cw-shadow-sm)}.update-icon svg{width:16px;height:16px}.update-text{display:flex;flex-direction:column;gap:1px;min-width:0;color:var(--cw-muted);font-size:12.5px;line-height:1.45}.update-text strong{color:var(--cw-ink);font-size:13px;font-weight:650}.update-command{grid-column:1/-1;display:flex;align-items:center;gap:8px;margin:8px 8px 0 0;padding:4px 4px 4px 10px;border:1px solid var(--cw-line);border-radius:9px;background:var(--cw-surface)}.update-command code{flex:1;min-width:0;color:var(--cw-ink);font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.update-bar .quiet.update{padding:5px 12px;border-radius:var(--cw-radius-pill)}
833
+ .update-bar{display:grid;grid-template-columns:30px 1fr auto;align-items:start;gap:4px 12px;padding:14px 12px 16px 20px;background:var(--cw-accent-soft);border-top-color:var(--cw-accent-line)}.update-icon{display:grid;place-items:center;width:30px;height:30px;border-radius:9px;background:var(--cw-surface);color:var(--cw-accent);box-shadow:var(--cw-shadow-sm)}.update-icon svg{width:16px;height:16px}.update-text{display:flex;flex-direction:column;gap:1px;min-width:0;color:var(--cw-muted);font-size:12.5px;line-height:1.45}.update-text strong{color:var(--cw-ink);font-size:13px;font-weight:650}.update-actions{grid-column:2/-1;display:flex;flex-wrap:wrap;gap:8px;margin:8px 8px 0 0}.update-actions .quiet{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;font-size:13px;font-weight:600;border-radius:var(--cw-radius-pill)}.update-actions svg{width:14px;height:14px}.update-toggle svg{color:var(--cw-ai)}.update-prompt{grid-column:1/-1;margin:10px 8px 0 0;max-height:220px;overflow:auto;padding:10px 12px;border:1px solid var(--cw-ai-line);border-radius:10px;background:linear-gradient(135deg,var(--cw-ai-soft),var(--cw-surface));color:var(--cw-ink);font:11.5px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre-wrap;overflow-wrap:anywhere;user-select:text;-webkit-user-select:text}.update-prompt:focus-visible{outline:2px solid var(--cw-accent);outline-offset:2px}
805
834
  @media(max-width:600px){.search input{font-size:16px}.select{max-width:100%}}
806
835
  @media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}
807
836
  `;
@@ -1667,6 +1696,7 @@ class Overlay {
1667
1696
  __publicField(this, "settingsCache", null);
1668
1697
  /** A newer npm version, found when the sidebar first opens. See checkForUpdate(). */
1669
1698
  __publicField(this, "update", null);
1699
+ __publicField(this, "updatePromptOpen", false);
1670
1700
  __publicField(this, "updateChecked", false);
1671
1701
  /** The page margin we replaced to make room for the sidebar, restored when it closes. */
1672
1702
  __publicField(this, "pushed", null);
@@ -1884,13 +1914,13 @@ class Overlay {
1884
1914
  });
1885
1915
  });
1886
1916
  var _a, _b, _c;
1887
- this.config = { primaryColor: "#0068f9", position: "bottom-right", keyboardShortcut: "alt+shift+c", enableKeyboardShortcuts: true, ...config };
1917
+ this.config = { primaryColor: "#0068f9", position: "bottom-right", keyboardShortcut: "alt+c", enableKeyboardShortcuts: true, ...config };
1888
1918
  if (!/^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(this.config.primaryColor ?? "")) {
1889
1919
  throw new Error("primaryColor must be a 3- or 6-digit hex color.");
1890
1920
  }
1891
1921
  if (this.config.theme !== void 0 && !THEMES.includes(this.config.theme)) throw new Error('theme must be "light", "dark" or "system".');
1892
1922
  this.shortcut = parseShortcut(this.config.keyboardShortcut ?? "");
1893
- if (this.config.enableKeyboardShortcuts && !this.shortcut) throw new Error('keyboardShortcut must be a key such as "alt+shift+c".');
1923
+ if (this.config.enableKeyboardShortcuts && !this.shortcut) throw new Error('keyboardShortcut must be a key such as "alt+c".');
1894
1924
  const project = config.projectId || location.hostname || "default";
1895
1925
  this.identityKey = `overplay:${project}:user`;
1896
1926
  this.launcherKey = `overplay:${project}:launcher`;
@@ -2727,7 +2757,9 @@ ${this.currentPage()}`;
2727
2757
  * Only development hosts check by default: that is where the person reading the notice can run npm.
2728
2758
  */
2729
2759
  checkForUpdate() {
2730
- if (this.updateChecked || !(this.config.updateNotice ?? developmentHost(location.hostname))) return;
2760
+ var _a;
2761
+ const scriptTag = typeof ((_a = globalThis.Overplay) == null ? void 0 : _a.init) === "function";
2762
+ if (this.updateChecked || !(this.config.updateNotice ?? (developmentHost(location.hostname) && !scriptTag))) return;
2731
2763
  this.updateChecked = true;
2732
2764
  void latestVersion().then((latest) => {
2733
2765
  if (this.destroyed || !latest || !isNewer(latest, VERSION)) return;
@@ -2740,19 +2772,24 @@ ${this.currentPage()}`;
2740
2772
  });
2741
2773
  }
2742
2774
  async copyUpdate() {
2775
+ const prompt = updatePrompt(VERSION, this.update ?? "latest");
2743
2776
  let copied = false;
2744
2777
  try {
2745
- await navigator.clipboard.writeText(UPDATE_COMMAND);
2778
+ await navigator.clipboard.writeText(prompt);
2746
2779
  copied = true;
2747
2780
  } catch {
2748
2781
  }
2782
+ if (!copied) {
2783
+ this.updatePromptOpen = true;
2784
+ this.render();
2785
+ }
2749
2786
  this.toast({
2750
2787
  kind: "comment",
2751
2788
  commentId: "",
2752
- author: copied ? `Update command copied` : "Run this to update",
2753
- text: copied ? `Run it in your project, then reload: ${UPDATE_COMMAND}` : UPDATE_COMMAND
2789
+ author: copied ? "Update prompt copied" : "Copy the prompt below",
2790
+ text: copied ? "Paste it into your coding agent. It updates the package, clears the cache and restarts the dev server." : "Select the prompt text and copy it."
2754
2791
  }, true);
2755
- this.announce(copied ? "Update command copied." : `Run ${UPDATE_COMMAND} to update.`);
2792
+ this.announce(copied ? "Update prompt copied." : "Select the prompt text to copy it.");
2756
2793
  }
2757
2794
  /** "Later" hides the notice until the next version comes out. */
2758
2795
  dismissUpdate() {
@@ -3468,6 +3505,10 @@ ${this.currentPage()}`;
3468
3505
  return empty;
3469
3506
  }
3470
3507
  /** A new npm version is the one thing left at the bottom. The account and settings live in the header. */
3508
+ /**
3509
+ * The update notice: an AI prompt that updates the package, clears the bundler cache and restarts the dev
3510
+ * server. A version bump alone often keeps serving the old widget.
3511
+ */
3471
3512
  footer() {
3472
3513
  if (!this.update) return null;
3473
3514
  const footer = element("div", "footer update-bar");
@@ -3475,16 +3516,24 @@ ${this.currentPage()}`;
3475
3516
  const mark = element("span", "update-icon");
3476
3517
  mark.append(icon("refresh"));
3477
3518
  const text = element("div", "update-text");
3478
- text.append(element("strong", "", `Version ${this.update} is available`), element("span", "", `You have ${VERSION}. Update the package, then reload.`));
3479
- const command = element("div", "update-command");
3480
- const code = element("code", "", UPDATE_COMMAND);
3481
- code.title = UPDATE_COMMAND;
3482
- const copy = button(`Copy the command to update to version ${this.update}`, "quiet update", () => {
3519
+ text.append(element("strong", "", `Version ${this.update} is available`), element("span", "", `You have ${VERSION}. Give this prompt to your coding agent to update.`));
3520
+ const actions = element("div", "update-actions");
3521
+ const show = button(this.updatePromptOpen ? "Hide Prompt" : "Show Prompt", "quiet update-toggle", () => {
3522
+ this.updatePromptOpen = !this.updatePromptOpen;
3523
+ this.render();
3524
+ }, "sparkle");
3525
+ show.setAttribute("aria-expanded", String(this.updatePromptOpen));
3526
+ const copy = button("Copy Prompt", "quiet update", () => {
3483
3527
  void this.copyUpdate();
3484
- });
3485
- copy.querySelector("span").textContent = "Copy";
3486
- command.append(code, copy);
3487
- footer.append(mark, text, button("Hide until the next version", "icon-button", () => this.dismissUpdate(), "close"), command);
3528
+ }, "copy");
3529
+ actions.append(show, copy);
3530
+ footer.append(mark, text, button("Hide until the next version", "icon-button", () => this.dismissUpdate(), "close"), actions);
3531
+ if (this.updatePromptOpen) {
3532
+ const prompt = element("pre", "update-prompt", updatePrompt(VERSION, this.update));
3533
+ prompt.tabIndex = 0;
3534
+ prompt.setAttribute("aria-label", "Update prompt for your coding agent");
3535
+ footer.append(prompt);
3536
+ }
3488
3537
  return footer;
3489
3538
  }
3490
3539
  messageHeader(name, timestamp, url, via, words2 = []) {
@@ -3637,7 +3686,6 @@ ${this.currentPage()}`;
3637
3686
  }
3638
3687
  /** Display choices for this project. Each person has their own, saved in this browser. */
3639
3688
  settingsView() {
3640
- var _a;
3641
3689
  const view = this.view("Settings", "settings");
3642
3690
  view.append(this.detailHeading("Settings", "Your choices for this project. Other people keep their own."));
3643
3691
  const current = this.settings, body = element("div", "settings-body");
@@ -3744,7 +3792,7 @@ ${this.currentPage()}`;
3744
3792
  row(panel, "Push the page aside", "The open panel does not cover the page.", toggle("pushContent", "Push the page aside"));
3745
3793
  const alerts = section("Notifications and keys");
3746
3794
  row(alerts, "New activity", "Show a short notice when others comment or reply.", toggle("notifications", "Show new activity"));
3747
- const keys = ((_a = this.config.keyboardShortcut) == null ? void 0 : _a.split("+").map((key2) => key2.length === 1 ? key2.toUpperCase() : key2[0].toUpperCase() + key2.slice(1)).join(" + ")) ?? "";
3795
+ const keys = shortcutLabel(this.config.keyboardShortcut ?? "", /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent));
3748
3796
  row(alerts, "Keyboard shortcut", this.shortcut ? `${keys} opens and closes comments.` : "This site has no shortcut.", toggle("shortcut", "Keyboard shortcut", !this.shortcut));
3749
3797
  const foot = element("div", "settings-foot");
3750
3798
  foot.append(element("span", "", "Saved in this browser"), button("Reset to Defaults", "quiet", () => this.resetSettings()));