@nanocollective/roster 0.1.0-alpha.16 → 0.1.0-alpha.18

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.16",
3
+ "version": "0.1.0-alpha.18",
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": {
@@ -109,8 +109,9 @@ main{padding:36px 44px 96px;max-width:1220px;overflow-x:hidden}
109
109
  .thit .hitline em{font-style:normal;color:var(--ink);background:color-mix(in srgb,var(--accent) 22%,transparent);
110
110
  border-radius:3px;padding:0 2px}
111
111
 
112
+ /* No height cap: a file grows the page, so there is one scrollbar, not one inside another. */
112
113
  .viewer{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-lg);padding:22px 26px;
113
- min-height:340px;max-height:74vh;overflow:auto;box-shadow:var(--shadow-sm)}
114
+ min-height:340px;min-width:0;box-shadow:var(--shadow-sm)}
114
115
  .viewer pre{margin:0;white-space:pre-wrap;word-break:break-word;font:12.5px/1.65 var(--mono);color:var(--ink-dim)}
115
116
  /* Source keeps its own line breaks and scrolls, rather than wrapping into soup. */
116
117
  .viewer pre.code{white-space:pre;word-break:normal;overflow-x:auto}
@@ -124,7 +124,7 @@
124
124
 
125
125
  /* ---------- the numbered to-do list ---------- */
126
126
 
127
- .todos{display:grid;gap:10px}
127
+ .todos{display:grid;gap:10px;margin-bottom:14px}
128
128
  .todo{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-lg);padding:16px 18px}
129
129
  .todo.done{padding:11px 18px;border-color:color-mix(in srgb,var(--accent) 30%,var(--line))}
130
130
  .todohead{display:flex;align-items:center;gap:11px}
@@ -14,11 +14,8 @@
14
14
  * wrong. The server validates it before writing.
15
15
  */
16
16
 
17
- import { highlighted } from "../mdedit.js";
18
- import { getFile, getOrgLayer, post } from "../api.js";
19
- import { askYes } from "../dialog.js";
20
- import { el, esc, grow, kb, skeleton } from "../dom.js";
21
- import { icon } from "../icons.js";
17
+ import { getFile, getOrgLayer } from "../api.js";
18
+ import { el, esc, kb, skeleton } from "../dom.js";
22
19
  import { mdlite } from "../md.js";
23
20
  import { humansOf, S, writeHash } from "../state.js";
24
21
  import { yamlPre } from "../yaml.js";
@@ -166,112 +163,25 @@ export function viewOrg(m) {
166
163
  viewer.replaceChildren();
167
164
  const head = el("div", { className: "row filehead" });
168
165
  head.append(el("span", { className: "meta", textContent: full(path) + " · " + kb(text.length) }));
169
- /* The edit control used to be a word in a row of grey text, which is why "there is no easy
170
- way to edit the org docs" was a fair thing to say about a screen that could edit them. */
171
166
  const file = S.orgFiles.find((f) => f.path === path) ?? { path };
172
167
 
173
- if (path.endsWith(".yaml") || path.endsWith(".yml")) {
174
- const edit = el("button", { className: "ghbtn primary editbtn", onclick: () => editor(path, text) });
175
- edit.append(icon("edit", "ic"), el("span", { textContent: "Edit" }));
176
- head.append(edit);
177
- viewer.append(head, el("p", { className: "factsub", textContent: why(file) }), yamlPre(text));
178
- return;
179
- }
180
-
181
- /* Prose files open on Read, with the ways to change them as tabs beside it: the same ones
182
- Getting started offers, because a file is no easier to write after setup than during it. */
168
+ const isYaml = /\.ya?ml$/.test(path);
169
+ /* Every file opens on Read, with the ways to change it as tabs beside it: the same ones
170
+ Getting started offers, because a file is no easier to write after setup than during it.
171
+ org.yaml is checked by the server before it is written, so a bad one is refused. */
183
172
  viewer.append(head, el("p", { className: "factsub", textContent: why(file) }));
184
- const read = { label: "Read", node: el("div", { className: "md doc", innerHTML: mdlite(text) }) };
173
+ const read = {
174
+ label: "Read",
175
+ node: isYaml ? yamlPre(text) : el("div", { className: "md doc", innerHTML: mdlite(text) }),
176
+ };
185
177
  const o = { path: full(path), name: S.data.name, text, before: [read], onSaved: () => show(path) };
186
178
  viewer.append(
187
179
  path === "org/business.md"
188
180
  ? businessForm(o)
189
181
  : path === "org/priorities.md"
190
182
  ? prioritiesForm(o)
191
- : fileModes(o),
192
- );
193
- }
194
-
195
- function editor(path, text) {
196
- viewer.replaceChildren();
197
- const isYaml = /\.ya?ml$/.test(path);
198
-
199
- const status = el("span", { className: "meta" });
200
- const save = el("button", { className: "ghbtn primary", textContent: "Save and commit" });
201
- const cancel = el("button", { className: "ghbtn", textContent: "Cancel" });
202
-
203
- const bar = el("div", { className: "row filehead" });
204
- bar.append(el("span", { className: "meta", textContent: full(path) }), save, cancel, status);
205
- viewer.append(bar);
206
-
207
- const ta = el("textarea", { value: text, className: "editor" });
208
- const hl = highlighted(ta, isYaml ? "yaml" : "md");
209
- viewer.append(hl);
210
- grow(ta);
211
- hl.repaint();
212
- ta.addEventListener("input", () => grow(ta));
213
- // ⌘S is what a person's hands do in a text box. Without it, saving means finding a button
214
- // above a screenful of textarea you have just scrolled past.
215
- ta.addEventListener("keydown", (e) => {
216
- if ((e.metaKey || e.ctrlKey) && e.key === "s") {
217
- e.preventDefault();
218
- commit();
219
- }
220
- });
221
- ta.focus?.();
222
-
223
- cancel.onclick = () => render(path, text);
224
- save.onclick = commit;
225
-
226
- viewer.append(
227
- el("p", {
228
- className: "editnote",
229
- textContent:
230
- "⌘S saves. Commits to " + S.data.opsName + " and pushes, and every staff member picks " +
231
- "it up on their next run." +
232
- (isYaml
233
- ? " org.yaml is checked before it is written: bad YAML, or a missing org or name, is refused rather than committed."
234
- : ""),
235
- }),
183
+ : fileModes({ ...o, lang: isYaml ? "yaml" : "md" }),
236
184
  );
237
-
238
- async function commit() {
239
- if (ta.value === text) {
240
- status.textContent = "nothing changed";
241
- status.className = "meta";
242
- return;
243
- }
244
- /* Only the manifest asks. A prose edit is one commit to revert and the agents pick it up
245
- on their next run; org.yaml is the file that can stop every prompt composing, and the
246
- portal has just told you it validates it. Asking every time taught people to click
247
- through the question, which is worse than not asking. */
248
- if (
249
- isYaml &&
250
- !(await askYes({
251
- title: "Commit " + full(path) + " and push?",
252
- hint: "Every staff member picks this up on their next run.",
253
- confirm: "Commit and push",
254
- }))
255
- ) {
256
- return;
257
- }
258
- save.disabled = cancel.disabled = true;
259
- status.textContent = "committing…";
260
- status.className = "meta";
261
- try {
262
- const r = await post({ path: full(path), text: ta.value }, "/api/save");
263
- status.textContent = r.pushed
264
- ? "committed and pushed · " + r.sha
265
- : "committed " + r.sha + ", but the push failed: " + (r.note ?? "");
266
- status.className = r.pushed ? "meta ok" : "meta warn";
267
- render(path, ta.value);
268
- } catch (e) {
269
- // A refused org.yaml is the interesting case: the reason is the whole message.
270
- status.textContent = e.message;
271
- status.className = "meta err";
272
- save.disabled = cancel.disabled = false;
273
- }
274
- }
275
185
  }
276
186
  }
277
187