codehost 0.21.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.22.1](https://github.com/snomiao/codehost/compare/v0.22.0...v0.22.1) (2026-06-15)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **web:** stack the setup-card command rows on narrow screens ([4348ef7](https://github.com/snomiao/codehost/commit/4348ef72d766aba4254ecf693277a6064b06d333))
7
+
8
+ # [0.22.0](https://github.com/snomiao/codehost/compare/v0.21.0...v0.22.0) (2026-06-14)
9
+
10
+
11
+ ### Features
12
+
13
+ * **web:** "Set up a machine" card on the home page ([c2cec58](https://github.com/snomiao/codehost/commit/c2cec580695cf759a986b18fc8fedd1494fc542f))
14
+
1
15
  # [0.21.0](https://github.com/snomiao/codehost/compare/v0.20.5...v0.21.0) (2026-06-13)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codehost",
3
- "version": "0.21.0",
3
+ "version": "0.22.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -196,6 +196,69 @@ function RoomClient(props: {
196
196
  return null;
197
197
  }
198
198
 
199
+ /** Track a `(max-width: …)` media query so inline-styled components can go
200
+ * responsive without a stylesheet. SSR-safe and listener-cleaned. */
201
+ function useNarrow(maxWidth = 560): boolean {
202
+ const [narrow, setNarrow] = useState(
203
+ () => typeof window !== "undefined" && window.matchMedia(`(max-width:${maxWidth}px)`).matches,
204
+ );
205
+ useEffect(() => {
206
+ const mq = window.matchMedia(`(max-width:${maxWidth}px)`);
207
+ const on = () => setNarrow(mq.matches);
208
+ on();
209
+ mq.addEventListener("change", on);
210
+ return () => mq.removeEventListener("change", on);
211
+ }, [maxWidth]);
212
+ return narrow;
213
+ }
214
+
215
+ /** A copy-to-clipboard command row: label, the command, and a Copy button. On
216
+ * narrow screens the three stack vertically so the long command doesn't get
217
+ * crushed between a fixed label and the button. */
218
+ function CopyCommand({ label, command }: { label: string; command: string }) {
219
+ const [copied, setCopied] = useState(false);
220
+ const narrow = useNarrow();
221
+ const copy = async () => {
222
+ try {
223
+ await navigator.clipboard.writeText(command);
224
+ } catch {
225
+ // clipboard blocked (insecure context / permission) — fall back to prompt
226
+ window.prompt("Copy this command:", command);
227
+ }
228
+ setCopied(true);
229
+ setTimeout(() => setCopied(false), 1500);
230
+ };
231
+ return (
232
+ <div style={{ ...styles.cmdRow, ...(narrow ? styles.cmdRowNarrow : null) }}>
233
+ <span style={{ ...styles.cmdLabel, ...(narrow ? styles.cmdLabelNarrow : null) }}>{label}</span>
234
+ <code style={styles.cmdCode}>{command}</code>
235
+ <button style={{ ...styles.cmdCopy, ...(narrow ? styles.cmdCopyNarrow : null) }} onClick={copy}>
236
+ {copied ? "Copied!" : "Copy"}
237
+ </button>
238
+ </div>
239
+ );
240
+ }
241
+
242
+ /**
243
+ * "Set up a machine" card: the one-liner that turns any machine into a codehost
244
+ * server. The script bootstraps everything (Bun, the CLI, VS Code, the daemon),
245
+ * so the user needs no prerequisites — not even Bun. setup.sh/.ps1 are aliases
246
+ * of install.* served by Pages (see public/_redirects).
247
+ */
248
+ function SetupCard() {
249
+ return (
250
+ <div style={styles.setupCard}>
251
+ <div style={styles.setupHead}>Set up a machine</div>
252
+ <p style={styles.setupSub}>
253
+ Run this on a machine to serve it here. It installs everything — Bun, VS Code, and the
254
+ codehost daemon — no prerequisites, and it picks a token and opens the browser for you.
255
+ </p>
256
+ <CopyCommand label="macOS / Linux" command="curl -fsSL https://codehost.dev/setup.sh | sh" />
257
+ <CopyCommand label="Windows" command={'powershell -c "irm codehost.dev/setup.ps1 | iex"'} />
258
+ </div>
259
+ );
260
+ }
261
+
199
262
  export function Discovery() {
200
263
  // Joined rooms — each token *is* a room id, and we keep one live signaling
201
264
  // client per room (see RoomClient). Seeded from the persisted room list plus
@@ -1070,12 +1133,10 @@ export function Discovery() {
1070
1133
  </span>
1071
1134
  )}
1072
1135
  </div>
1073
- {tokens.length === 0 && <p style={styles.dim}>Join a room to see your workspaces.</p>}
1074
1136
  {tokens.length > 0 && serverCount === 0 && (
1075
- <p style={styles.dim}>
1076
- No servers online. Run <code style={styles.code}>bunx codehost serve -t &lt;token&gt;</code> on a machine.
1077
- </p>
1137
+ <p style={styles.dim}>No servers online in your rooms yet.</p>
1078
1138
  )}
1139
+ {serverCount === 0 && <SetupCard />}
1079
1140
  {serverCount > 0 && (
1080
1141
  <>
1081
1142
  <input
@@ -1225,6 +1286,10 @@ export function Discovery() {
1225
1286
  </p>
1226
1287
  </section>
1227
1288
  )}
1289
+
1290
+ {/* When servers already exist the empty-state card is hidden, so keep an
1291
+ "add another machine" affordance available down here. */}
1292
+ {serverCount > 0 && <SetupCard />}
1228
1293
  </main>
1229
1294
  </div>
1230
1295
  </>
@@ -1312,6 +1377,16 @@ const styles: Record<string, React.CSSProperties> = {
1312
1377
  echoBad: { marginTop: 6, fontSize: 12, color: "#f48771", fontFamily: "monospace" },
1313
1378
  rosterSection: { marginTop: 28 },
1314
1379
  rosterHead: { fontSize: 14, color: "#aaa", fontWeight: 600, margin: "0 0 12px" },
1380
+ setupCard: { marginTop: 20, background: "#252525", border: "1px solid #3d3d3d", borderRadius: 8, padding: "16px 18px" },
1381
+ setupHead: { fontSize: 15, color: "#fff", fontWeight: 600, marginBottom: 6 },
1382
+ setupSub: { fontSize: 13, color: "#aaa", margin: "0 0 14px", lineHeight: 1.5 },
1383
+ cmdRow: { display: "flex", alignItems: "center", gap: 10, marginTop: 8 },
1384
+ cmdRowNarrow: { flexDirection: "column", alignItems: "stretch", gap: 6, marginTop: 12 },
1385
+ cmdLabel: { fontSize: 11, color: "#888", width: 88, flexShrink: 0 },
1386
+ cmdLabelNarrow: { width: "auto" },
1387
+ cmdCode: { flex: 1, minWidth: 0, background: "#1b1b1b", border: "1px solid #3d3d3d", borderRadius: 6, padding: "8px 10px", fontFamily: "monospace", fontSize: 12.5, color: "#dcdcaa", overflow: "auto", whiteSpace: "nowrap" },
1388
+ cmdCopy: { flexShrink: 0, background: "#0e639c", border: "none", color: "#fff", padding: "8px 12px", borderRadius: 6, cursor: "pointer", fontSize: 12 },
1389
+ cmdCopyNarrow: { width: "100%", padding: "10px 12px" },
1315
1390
  rosterHint: { margin: "10px 0 0", fontSize: 12, color: "#888" },
1316
1391
  personRow: { display: "flex", alignItems: "center", gap: 10, background: "#252525", border: "1px solid #3d3d3d", borderRadius: 8, padding: "8px 14px", fontSize: 13 },
1317
1392
  personDot: { color: "#4ec9b0", fontSize: 10 },