@nanocollective/roster 0.1.0-alpha.14 → 0.1.0-alpha.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanocollective/roster",
3
- "version": "0.1.0-alpha.14",
3
+ "version": "0.1.0-alpha.15",
4
4
  "description": "An agent-run org, powered by GitHub. Scaffold AI staff members whose brain is a repo.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -141,5 +141,10 @@
141
141
  .qfield span{font:600 12.5px var(--sans)}
142
142
  .qfield input{background:var(--bg-elev);border:1px solid var(--border-strong);color:var(--ink);
143
143
  border-radius:var(--r);padding:8px 10px;font:13px var(--sans)}
144
- .altway{margin-top:14px}
145
- .altway summary{cursor:pointer;color:var(--ink-dim);font-size:12.5px}
144
+ .modes{display:inline-flex;gap:2px;padding:3px;margin:0 0 14px;background:var(--surface-2);
145
+ border-radius:var(--r);flex-wrap:wrap}
146
+ .mode{border:0;background:none;color:var(--ink-dim);font:600 12.5px var(--sans);padding:6px 12px;
147
+ border-radius:calc(var(--r) - 2px);cursor:pointer}
148
+ .mode:hover{color:var(--ink)}
149
+ .mode.on{background:var(--bg-elev);color:var(--ink);box-shadow:var(--shadow-sm)}
150
+ .modes + div > .paste{border-top:0;margin-top:0;padding-top:0}
@@ -492,7 +492,7 @@ const BUSINESS_QUESTIONS = [
492
492
 
493
493
  /** The business, as five questions in plain fields, written out as org/business.md. */
494
494
  function businessForm(onSaved) {
495
- const box = el("div");
495
+ const form = el("div");
496
496
  const path = opsName() + "/org/business.md";
497
497
  const fields = BUSINESS_QUESTIONS.map(([id, label, , required]) => {
498
498
  const input = id === "line" ? el("input", { type: "text" }) : el("textarea", { className: "pastebox", rows: 3 });
@@ -504,7 +504,7 @@ function businessForm(onSaved) {
504
504
  });
505
505
  const save = el("button", { className: "btn primary", textContent: "Save" });
506
506
  const out = el("div");
507
- box.append(...fields.map((f) => f.wrap), el("div", { className: "row" }, [save]), out);
507
+ form.append(...fields.map((f) => f.wrap), el("div", { className: "row" }, [save]), out);
508
508
 
509
509
  save.onclick = async () => {
510
510
  const missing = fields.find((f, i) => BUSINESS_QUESTIONS[i][3] && !String(f.input.value ?? "").trim());
@@ -524,20 +524,19 @@ function businessForm(onSaved) {
524
524
  await saveAndSay(save, out, path, text, "portal: write org/business.md", onSaved);
525
525
  };
526
526
 
527
- // For somebody who would rather be interviewed than fill in a form.
528
- box.append(
529
- el("details", { className: "altway" }, [
530
- el("summary", { textContent: "Or let an AI interview you instead" }),
531
- paste({ kind: "discover", title: "", onSaved: () => (onSaved(), recheck()) }),
532
- ]),
533
- );
534
- box.append(changeRaw(path, onSaved));
535
- return box;
527
+ return modes([
528
+ { label: "Answer questions", node: form },
529
+ {
530
+ label: "Let an AI interview you",
531
+ node: paste({ kind: "discover", title: "", onSaved: () => (onSaved(), recheck()) }),
532
+ },
533
+ rawMode(path, onSaved),
534
+ ]);
536
535
  }
537
536
 
538
537
  /** This month's priorities, as three lines and an out-of-scope list. */
539
538
  function prioritiesForm(onSaved) {
540
- const box = el("div");
539
+ const form = el("div");
541
540
  const path = opsName() + "/org/priorities.md";
542
541
  const ranks = [1, 2, 3].map((i) => {
543
542
  const input = el("input", { type: "text", placeholder: i === 1 ? "e.g. Tasks can be ticked off and removed" : "" });
@@ -546,7 +545,7 @@ function prioritiesForm(onSaved) {
546
545
  const scope = el("textarea", { className: "pastebox", rows: 3, placeholder: "One per line" });
547
546
  const save = el("button", { className: "btn primary", textContent: "Save" });
548
547
  const out = el("div");
549
- box.append(
548
+ form.append(
550
549
  ...ranks.map((r) => r.wrap),
551
550
  el("label", { className: "qfield" }, [el("span", { textContent: "Out of scope this month (optional)" }), scope]),
552
551
  el("div", { className: "row" }, [save]),
@@ -567,8 +566,7 @@ function prioritiesForm(onSaved) {
567
566
  (outs.length ? "\n### Out of scope this month\n\n" + outs.map((t) => `- ${t}`).join("\n") + "\n" : "");
568
567
  await saveAndSay(save, out, path, text, "portal: write org/priorities.md", onSaved);
569
568
  };
570
- box.append(changeRaw(path, onSaved));
571
- return box;
569
+ return modes([{ label: "Fill in", node: form }, rawMode(path, onSaved)]);
572
570
  }
573
571
 
574
572
  async function saveAndSay(btn, out, path, text, message, onSaved) {
@@ -594,18 +592,18 @@ async function saveAndSay(btn, out, path, text, message, onSaved) {
594
592
  }
595
593
 
596
594
  /** The file itself, for editing what is already there rather than starting again. */
597
- function changeRaw(path, onSaved) {
598
- const details = el("details", { className: "altway" });
599
- details.append(el("summary", { textContent: "Or edit the file directly" }));
600
- let loaded = false;
601
- details.addEventListener("toggle", () => {
602
- if (!details.open || loaded) return;
603
- loaded = true;
604
- const ta = el("textarea", { className: "pastebox" });
605
- ta.rows = 12;
606
- const save = el("button", { className: "btn", textContent: "Save" });
607
- const out = el("div");
608
- details.append(ta, el("div", { className: "row" }, [save]), out);
595
+ function rawMode(path, onSaved) {
596
+ const node = el("div");
597
+ const ta = el("textarea", { className: "pastebox" });
598
+ ta.rows = 12;
599
+ const save = el("button", { className: "btn primary", textContent: "Save" });
600
+ const out = el("div");
601
+ node.append(ta, el("div", { className: "row" }, [save]), out);
602
+ ta.addEventListener("input", () => grow(ta));
603
+ save.onclick = () =>
604
+ saveAndSay(save, out, path, ta.value, "portal: edit " + path.split("/").slice(1).join("/"), onSaved);
605
+ // Read when first shown, so it shows the file as it is then, not as it was at page load.
606
+ const load = () =>
609
607
  // Not getFile: an absent file answers 404 with a message, and that is not what to edit.
610
608
  fetch(fileUrl(path), { cache: "no-store" })
611
609
  .then((r) => (r.ok ? r.text() : ""))
@@ -613,10 +611,28 @@ function changeRaw(path, onSaved) {
613
611
  ta.value = text;
614
612
  grow(ta);
615
613
  });
616
- ta.addEventListener("input", () => grow(ta));
617
- save.onclick = () => saveAndSay(save, out, path, ta.value, "portal: edit " + path.split("/").slice(1).join("/"), onSaved);
614
+ return { label: "Edit the file", node, onShow: load };
615
+ }
616
+
617
+ /** Ways to do one step, as a switcher across the top: one shown at a time. */
618
+ function modes(list) {
619
+ const box = el("div");
620
+ const bar = el("div", { className: "modes" });
621
+ const panes = el("div");
622
+ const buttons = list.map((m, i) => {
623
+ const b = el("button", { className: "mode", textContent: m.label });
624
+ b.onclick = () => show(i);
625
+ return b;
618
626
  });
619
- return details;
627
+ bar.append(...buttons);
628
+ box.append(bar, panes);
629
+ function show(i) {
630
+ buttons.forEach((b, j) => b.classList.toggle("on", i === j));
631
+ panes.replaceChildren(list[i].node);
632
+ list[i].onShow?.();
633
+ }
634
+ show(0);
635
+ return box;
620
636
  }
621
637
 
622
638
  /** The ops repo's directory name, which is what every workspace-relative path starts with. */