Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions cloud/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,12 @@ cd "$HOME"
mkdir -p code
note() { echo "$(date -u +%FT%TZ) $*" >> "$HOME/box.log"; }
# Nothing runs git yet, so a lock in a repo is one a process killed by the restart left. Pane ids
# start again at %0, so one noted before this boot could be another program's by now.
# start again at %0, so one noted before this boot could be another program's by now; its mark
# (supervisor-pane-role) goes with it.
find "$HOME/code" -path '*/.git/*' -name '*.lock' -type f -delete 2>/dev/null
# so is a Postgres pid file: its old pid is often held by another process by now, and it wouldn't start
rm -f "$HOME"/.pg/*/postmaster.pid
rm -f "$HOME"/code/*/docs/devteam/run/supervisor-pane
rm -f "$HOME"/code/*/docs/devteam/run/supervisor-pane "$HOME"/code/*/docs/devteam/run/supervisor-pane-role
# tmux starts login shells, which read .profile, and Debian's reads .bashrc
[ -f .profile ] || cp /etc/skel/.profile .profile
[ -f .tmux.conf ] || printf 'set -g mouse on\nset -g history-limit 50000\nset -g default-terminal tmux-256color\nset -g default-shell /bin/bash\n' > .tmux.conf
Expand Down
17 changes: 14 additions & 3 deletions docs/devteam/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4606,13 +4606,21 @@ <h4>${linkify(k.statement)}</h4>
// A question asked in the thread: the Inbox's own card while it is open there (from /api/data),
// answered from either place once; once it isn't open, its answer. A review or a secret is answered
// in the Inbox, where its page or its form is: two forms that post values is one too many.
// A question as /api/data has it once it isn't open (only its 80 newest such: ooda.answered), or
// nothing: answered elsewhere (the terminal, another tab), the page's data can say so before the
// card's read of the threads does.
const dataAnswered = (id) => (DATA?.ooda?.answered || []).find((x) => x.id === id);
function tlQuestion(q, reasonText) {
if (ANSWERED.has(q.id)) return `<div class="card small tl-q done"><div class="answered-note"><i class="icon-circle-check" aria-hidden="true"></i> Answered: <b>${esc(ANSWERED.get(q.id))}</b></div></div>`;
const open = (DATA?.questions || []).find((x) => x.id === q.id);
if (q.status === 'open' && open && open.kind !== 'review' && !(open.secrets || []).length) return questionCard(open, DATA.ooda, reasonText, true);
const said = q.status === 'open' && !open ? dataAnswered(q.id) : null;
if (said) return tlDone(q, said);
if (q.status === 'open') return `<div class="card small tl-q"><b>${qinline(q.question, q.links)}</b><div class="meta"><a class="linkish" href="#q/${encodeURIComponent(q.id)}">Answer it in the Inbox</a></div></div>`;
return `<div class="card small tl-q"><div>${qinline(q.question, q.links)}</div>${q.answer ? `<div><b>${esc(q.answer)}</b></div>` : ''}<div class="meta">${esc(q.status === 'answered' ? `answered ${CHANNEL_WORDS[q.channel] || ''}` : q.status)}</div></div>`;
return tlDone(q, q);
}
// A question that isn't open, with its answer and where from as a (the thread's copy, or the page's data) has them.
const tlDone = (q, a) => `<div class="card small tl-q"><div>${qinline(q.question, q.links)}</div>${a.answer ? `<div><b>${esc(a.answer)}</b></div>` : ''}<div class="meta">${esc(a.status === 'answered' ? `answered ${CHANNEL_WORDS[a.channel] || ''}` : a.status)}</div></div>`;
// A turn's words, or the question it asks.
function tlTurnBody(u, t) {
const q = u.question ? t.questions.find((x) => x.id === u.question) : null;
Expand Down Expand Up @@ -4643,7 +4651,7 @@ <h4>${linkify(k.statement)}</h4>
// seen only when the owner can see it: a reply landing in a hidden tab still gets its dot
if (t && document.visibilityState === 'visible') tlSeen(t);
const turns = tlEl('tl-c-turns');
const sig = JSON.stringify([t, s, title, (t?.questions || []).map((q) => [q.id, (DATA?.questions || []).some((x) => x.id === q.id), ANSWERED.has(q.id)])]);
const sig = JSON.stringify([t, s, title, (t?.questions || []).map((q) => [q.id, (DATA?.questions || []).some((x) => x.id === q.id), !!dataAnswered(q.id), ANSWERED.has(q.id)])]);
if (sig !== TL.sig && !turns.contains(document.activeElement)) {
TL.sig = sig;
const now = t ? tlHome(s) : null;
Expand Down Expand Up @@ -5151,7 +5159,10 @@ <h4>${linkify(k.statement)}</h4>
render(shown);
if (first) route();
inboxTitle(shown);
// the thread layer, which the redraw doesn't touch: its pins on what was just drawn, then its threads again
// the thread layer, which the redraw doesn't touch: the open card as this data has its questions
// (one answered elsewhere shows answered now, not at the next read), its pins on what was just
// drawn, then its threads again
tlCard();
tlPins();
tlRefresh();
} catch (e) {
Expand Down
51 changes: 40 additions & 11 deletions docs/devteam/scripts/devteam.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6946,20 +6946,49 @@ function artifactSource(at, slug) {
function artifactAt(at, slug) {
if (!String(at).startsWith('tree:')) return artifactSource(at, slug);
const tree = at.slice(5);
if (!LINK_ID.test(slug ?? '') || !TREE_ID.test(tree) || !artifactTreeOkSync(tree)) return null;
if (!LINK_ID.test(slug ?? '') || !TREE_ID.test(tree) || !artifactTreeOkSync(tree, slug)) return null;
const entries = treeIdEntries(tree);
return entries.length ? { entries } : null;
}
// Is this id an artifact folder's tree? A commit's id, or the root tree's, would let <id>:<path> name
// any committed file, so it must be a tree with an artifact.json at its top (decision 12; its slug
// can't be checked: a folder's tree doesn't know its name, and artifact.json doesn't say it).
// Remembered once true: a tree id never changes.
// Is this id an artifact folder's tree, and was it <slug>'s folder on main? A commit's id, or the
// root tree's, would let <id>:<path> name any committed file, so it must be a tree with an
// artifact.json at its top (decision 12). And another artifact's tree under this slug would show its
// files under this one's name, so it must be main's tree for that folder now, or have been, by main's
// history of the folder: only the commits that touch it are walked, a merge against main's previous
// state. Remembered once true (a tree id never changes, and main's history only grows); a no is asked
// again, since main can come to have it. A shallow clone's history stops short, so an older version
// there is no one's, and the viewer shows main. gitReadCache, when a read-only command turns it on,
// keeps these reads for that one process only.
const artifactTrees = new Set();
const artifactTreeOk = (id) => (artifactTrees.has(id) ? Promise.resolve(true) : new Promise((resolve) => execFile('git', ['cat-file', '-t', id], { cwd: mainRoot, encoding: 'utf8' }, (e1, type) => {
if (e1 || type.trim() !== 'tree') return resolve(false);
return execFile('git', ['cat-file', '-e', `${id}:artifact.json`], { cwd: mainRoot }, (e2) => { if (!e2) artifactTrees.add(id); resolve(!e2); });
})));
const artifactTreeOkSync = (id) => artifactTrees.has(id) || (git(['cat-file', '-t', id], mainRoot) === 'tree' && git(['cat-file', '-e', `${id}:artifact.json`], mainRoot) !== null && !!artifactTrees.add(id));
const treeFolderArgs = (ref, slug) => ({
now: ['ls-tree', ref, '--', `${ARTIFACTS_REL}/${slug}`],
log: ['log', '--format=', '--raw', '--no-abbrev', '-t', '-m', '--first-parent', '--no-renames', ref, '--', `${ARTIFACTS_REL}/${slug}`],
});
const treeNow = (ls) => /^040000 tree ([0-9a-f]+)\t/.exec(ls ?? '')?.[1] ?? null;
const treeInHistory = (raw, slug, tree) => (raw ?? '').split('\n').some((l) => { const m = /^:\d+ \d+ [0-9a-f]+ ([0-9a-f]+) \S+\t(.*)$/.exec(l); return !!m && m[1] === tree && m[2] === `${ARTIFACTS_REL}/${slug}`; });
const gitAsync = (args) => new Promise((resolve) => execFile('git', args, { cwd: mainRoot, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }, (e, so) => resolve(e ? null : so)));
async function artifactTreeOk(id, slug) {
const key = `${slug}\0${id}`;
if (artifactTrees.has(key)) return true;
if ((await gitAsync(['cat-file', '-t', id]))?.trim() !== 'tree' || (await gitAsync(['cat-file', '-e', `${id}:artifact.json`])) === null) return false;
const ref = mainRef();
if (!ref) return false;
const a = treeFolderArgs(ref, slug);
const ok = treeNow(await gitAsync(a.now)) === id || treeInHistory(await gitAsync(a.log), slug, id);
if (ok) artifactTrees.add(key);
return ok;
}
function artifactTreeOkSync(id, slug) {
const key = `${slug}\0${id}`;
if (artifactTrees.has(key)) return true;
if (git(['cat-file', '-t', id], mainRoot) !== 'tree' || git(['cat-file', '-e', `${id}:artifact.json`], mainRoot) === null) return false;
const ref = mainRef();
if (!ref) return false;
const a = treeFolderArgs(ref, slug);
const ok = treeNow(git(a.now, mainRoot)) === id || treeInHistory(git(a.log, mainRoot), slug, id);
if (ok) artifactTrees.add(key);
return ok;
}
// A broken manifest is listed by its slug and error, never its unvalidated title.
function describeArtifact(slug, at, src) {
const p = artifactProblems(slug, src.entries);
Expand Down Expand Up @@ -13299,7 +13328,7 @@ async function cmdDashboard() {
// an artifact folder's tree by its id, and nothing else (decision 12): <id>:<path> resolves
// against a commit or the root tree as well, which would serve any committed file. A tree
// never changes: what was published is what is shown.
if (await artifactTreeOk(am[2])) bytes = await blobAt(`${am[2]}:${rel}`);
if (await artifactTreeOk(am[2], am[3])) bytes = await blobAt(`${am[2]}:${rel}`);
} else {
// the real path stays inside the artifact's folder: a symlink out of it is not found
try {
Expand Down
Loading
Loading