heyquilt 0.3.121 → 0.3.138
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/RELEASES.md +292 -17
- package/bin/quilt.js +72 -5
- package/desktop/main.js +16 -2
- package/package.json +3 -1
- package/src/agent-task-workflow.js +2 -2
- package/src/agents/common.js +13 -0
- package/src/ai-vm.js +9 -1
- package/src/api/activity.js +5 -0
- package/src/api/memory-store.js +21 -10
- package/src/api/notify.js +134 -0
- package/src/api/routes/browsers.js +29 -0
- package/src/api/routes/github.js +0 -13
- package/src/api/routes/relay.js +26 -3
- package/src/api/routes/seam-connect.js +103 -0
- package/src/api/routes/session-migrate.js +39 -0
- package/src/api/routes/sessions.js +73 -1
- package/src/api/server.js +46 -12
- package/src/api/supabase-store.js +53 -4
- package/src/autowork.js +182 -16
- package/src/connection.js +39 -9
- package/src/control.js +139 -13
- package/src/force-date.js +27 -0
- package/src/fsutil.js +13 -3
- package/src/gitstate.js +38 -2
- package/src/history.js +25 -1
- package/src/mcp.js +93 -7
- package/src/merge3.js +11 -3
- package/src/now-summary.js +113 -0
- package/src/pathrules.js +12 -0
- package/src/protocol.js +10 -2
- package/src/quilt-default.js +84 -0
- package/src/quilt-history.js +106 -0
- package/src/quilt-remote.js +78 -0
- package/src/relay-commit.js +5 -0
- package/src/relay-lease.js +6 -2
- package/src/relay-mcp.js +172 -8
- package/src/relay-outbound.js +1 -1
- package/src/relay-prune.js +4 -1
- package/src/release-words.js +2 -2
- package/src/runner.js +20 -7
- package/src/seam-access.js +97 -0
- package/src/seam-claims.js +53 -0
- package/src/seam-url.js +15 -0
- package/src/server.js +1767 -33
- package/src/session-follow.js +515 -0
- package/src/session-layout3.js +466 -0
- package/src/session-migrate.js +123 -0
- package/src/session-quilt.js +145 -0
- package/src/session-seam.js +284 -0
- package/src/session.js +899 -46
- package/src/source-live/bring-in.js +283 -0
- package/src/source-live/live-block.js +507 -35
- package/src/source-live/live-square.js +17 -6
- package/src/status.js +5 -0
- package/src/ui/agent-guide.js +1 -1
- package/src/ui/app.css +85 -20
- package/src/ui/app.js +4 -1
- package/src/ui/chat.js +9 -3
- package/src/ui/cloud-ai-model.js +13 -0
- package/src/ui/cloud-ai.js +10 -2
- package/src/ui/common.js +6 -2
- package/src/ui/fileview.js +20 -7
- package/src/ui/force-words.js +19 -0
- package/src/ui/git-rule.js +9 -0
- package/src/ui/home.js +0 -147
- package/src/ui/loom-model.js +15 -1
- package/src/ui/quiltview.js +213 -0
- package/src/ui/seam-model.js +251 -0
- package/src/ui/session.js +413 -31
- package/src/ui/voice-call.js +83 -14
- package/src/ui/voice-model.js +39 -0
- package/src/ui/voice.js +167 -17
- package/src/ui-server.js +80 -28
- package/src/update-check.js +25 -1
- package/src/voice.js +13 -6
- package/src/git.js +0 -147
- package/src/quilts/AGENTS.md +0 -1
- package/src/quilts/CLAUDE.md +0 -1
- package/src/quilts/archiver.js +0 -175
- package/src/quilts/autorevert.js +0 -49
- package/src/quilts/bisect-run.js +0 -31
- package/src/quilts/blame.js +0 -103
- package/src/quilts/block.js +0 -347
- package/src/quilts/bucket.js +0 -25
- package/src/quilts/cbor.js +0 -92
- package/src/quilts/claims.js +0 -144
- package/src/quilts/cli.js +0 -449
- package/src/quilts/client.js +0 -128
- package/src/quilts/conflict-format.js +0 -190
- package/src/quilts/describe.js +0 -266
- package/src/quilts/dogfood.js +0 -82
- package/src/quilts/errors.js +0 -8
- package/src/quilts/events.js +0 -160
- package/src/quilts/export-git.js +0 -130
- package/src/quilts/export.js +0 -81
- package/src/quilts/fastmerge.js +0 -41
- package/src/quilts/hash.js +0 -44
- package/src/quilts/history.js +0 -266
- package/src/quilts/ids.js +0 -12
- package/src/quilts/import-git.js +0 -291
- package/src/quilts/import.js +0 -134
- package/src/quilts/index.js +0 -72
- package/src/quilts/limits.js +0 -67
- package/src/quilts/lines.js +0 -49
- package/src/quilts/live.js +0 -50
- package/src/quilts/merge-smart.js +0 -114
- package/src/quilts/merge.js +0 -486
- package/src/quilts/objects.js +0 -144
- package/src/quilts/pack.js +0 -118
- package/src/quilts/records.js +0 -211
- package/src/quilts/resolutions.js +0 -69
- package/src/quilts/reviews.js +0 -195
- package/src/quilts/routes.js +0 -126
- package/src/quilts/service.js +0 -923
- package/src/quilts/setup.js +0 -34
- package/src/quilts/signing.js +0 -149
- package/src/quilts/squares.js +0 -239
- package/src/quilts/store-memory.js +0 -683
- package/src/quilts/store-rpc.js +0 -79
- package/src/quilts/store-supabase.js +0 -13
- package/src/quilts/tags.js +0 -64
- package/src/quilts/textdiff.js +0 -96
- package/src/quilts/tokens.js +0 -66
- package/src/quilts/tools.js +0 -375
- package/src/quilts/tree.js +0 -351
- package/src/quilts/webhook.js +0 -116
- package/src/quilts/window.js +0 -119
- package/src/quilts/xdiff.js +0 -435
- package/src/ui/branches.js +0 -111
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// The app in a session that moved into Seam (layout 3, Seam's plans/quilt-source-control/16): what the
|
|
2
|
+
// source control button, its panel and Session settings show there. Each folder follows its branch's
|
|
3
|
+
// square (session-follow.js), and work lands by committing the square and merging it onto main, in
|
|
4
|
+
// Seam; git is set aside in the folder and GitHub only receives Seam's export of main. So nothing here
|
|
5
|
+
// offers git, branches, pushes or pull requests. Pure: no DOM (test/unit/ui-seam-model.test.js).
|
|
6
|
+
|
|
7
|
+
import { forceAheadWords } from './force-words.js'
|
|
8
|
+
|
|
9
|
+
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]))
|
|
10
|
+
|
|
11
|
+
/** Whether the session's files are in Seam's squares now (the relay says layout 3): status.quilt from Session.quiltLink. */
|
|
12
|
+
export const inSeam = (st) => !!(st && st.quilt && st.quilt.layout === 3)
|
|
13
|
+
|
|
14
|
+
/** The quilt's name as people read it. */
|
|
15
|
+
const quiltName = (st) => (st.quilt && (st.quilt.slug || st.quilt.id)) || 'the quilt'
|
|
16
|
+
|
|
17
|
+
/** The square this folder follows, as Seam names it (`sq:` and the end of its id), or ''. */
|
|
18
|
+
export const squareName = (st) => (st && st.square && st.square.short ? `sq:${st.square.short}` : '')
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The source control button at layout 3: { label, title, aria } for the square this folder follows, or
|
|
22
|
+
* why it follows none yet. `label` '' leaves the button with its icon and count only.
|
|
23
|
+
*/
|
|
24
|
+
export function seamChip (st) {
|
|
25
|
+
const name = squareName(st)
|
|
26
|
+
const s = st.square
|
|
27
|
+
const label = name
|
|
28
|
+
const title = st.moving ? movingCard(st).text
|
|
29
|
+
: st.squareless ? squarelessWords(st).text
|
|
30
|
+
: st.readOnly && !(s && !s.opening && !s.problem)
|
|
31
|
+
? `Your edits here are not reaching anyone: ${st.readOnly.why}`
|
|
32
|
+
: !s ? `This session's code is committed and merged in Seam (${quiltName(st)}), and this folder is not following a square yet.`
|
|
33
|
+
: s.problem ? `This folder's work belongs in square ${name}, but it can't follow it yet: ${s.problem}`
|
|
34
|
+
: s.opening ? `This folder is starting to follow square ${name}.`
|
|
35
|
+
: [`This folder's work is live in square ${name} of ${quiltName(st)}. Click to commit it or merge it onto main.`, ...catchUpWords(s)].join(' ')
|
|
36
|
+
// The number is the square's work not committed yet (everyone's on it): what a commit takes.
|
|
37
|
+
const count = s && s.work ? s.work.count : 0
|
|
38
|
+
const countLine = count ? `${files(count)} not committed in this square.` : ''
|
|
39
|
+
return { label, title, aria: `Source control: ${name ? `square ${name}` : 'Seam'}${count ? `, ${files(count)} not committed` : ''}`, count, countLine }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const files = (n) => `${n} file${n === 1 ? '' : 's'}`
|
|
43
|
+
const patches = (n) => `${n} newer patch${n === 1 ? '' : 'es'}`
|
|
44
|
+
const listed = (list) => list.length > 4 ? `${list.slice(0, 4).join(', ')} and ${list.length - 4} more` : list.join(', ')
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Main brought into the square (session-follow.js status: `behind`, `conflicted`, `brought`), in plain
|
|
48
|
+
* words: how far behind main it is, when main's newer patches last came in, and what conflicts with main.
|
|
49
|
+
* The app's square card and panel show these, and quilt_status says them. [] with nothing to say.
|
|
50
|
+
*/
|
|
51
|
+
export function catchUpWords (s) {
|
|
52
|
+
if (!s) return []
|
|
53
|
+
const out = []
|
|
54
|
+
const b = s.brought
|
|
55
|
+
const old = Array.isArray(s.outdated) ? s.outdated : []
|
|
56
|
+
if (s.behind && old.length) out.push(`Main has ${patches(s.behind)} this square doesn't have yet. ${s.behind === 1 ? 'It comes' : 'They come'} in once ${listed(old)} ${old.length === 1 ? 'updates' : 'update'} Quilt: the version on ${old.length === 1 ? 'that folder' : 'those folders'} can't follow a square whose base moves.`)
|
|
57
|
+
else if (s.behind) out.push(`Main has ${patches(s.behind)} this square doesn't have yet: Quilt brings ${s.behind === 1 ? 'it' : 'them'} in by itself.`)
|
|
58
|
+
if (b && b.at) {
|
|
59
|
+
const at = new Date(b.at).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
|
|
60
|
+
const combined = b.combined || []
|
|
61
|
+
out.push(`Main's ${b.patches ? patches(b.patches) : 'newer patches'} came in at ${at}${b.by ? ` (brought in by ${b.by})` : ''}${combined.length ? `, combined with this square's work in ${listed(combined)}` : ''}.`)
|
|
62
|
+
const stale = Array.isArray(b.stale) ? b.stale : []
|
|
63
|
+
if (stale.length) out.push(`${files(stale.length)} in this square ${stale.length === 1 ? 'was an older copy' : 'were older copies'} of main's, not anyone's edit: main's ${stale.length === 1 ? 'version stands' : 'versions stand'} (${listed(stale)}).`)
|
|
64
|
+
}
|
|
65
|
+
const n = s.conflicted ?? (b && b.conflicts ? b.conflicts.length : 0)
|
|
66
|
+
if (n) {
|
|
67
|
+
const named = b && b.conflicts && b.conflicts.length ? ` (${listed(b.conflicts)})` : ''
|
|
68
|
+
out.push(`${files(n)} ${n === 1 ? 'conflicts' : 'conflict'} with main and ${n === 1 ? 'is' : 'are'} marked in this folder${named}: settle ${n === 1 ? 'it' : 'them'}, commit, and merge again.`)
|
|
69
|
+
}
|
|
70
|
+
return out
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The square card above the main area: { title, text }, only when something needs the person (a
|
|
75
|
+
* conflict with main, edits reaching nobody, a move under way), else null. The app's words, not
|
|
76
|
+
* quilt_status's: a person commits and merges from the square button, not with a command.
|
|
77
|
+
*/
|
|
78
|
+
export function squareCard (st) {
|
|
79
|
+
// The owner is moving the session (into Seam, or back): said first, with what to expect, on git or in Seam alike.
|
|
80
|
+
if (st.moving) return movingCard(st)
|
|
81
|
+
const s = st.square
|
|
82
|
+
const following = s && !s.opening && !s.problem
|
|
83
|
+
// The relay's own words name a tool: a person gets plain ones, and the button in the square panel.
|
|
84
|
+
if (st.squareless) return { ...squarelessWords(st), open: true }
|
|
85
|
+
if (st.readOnly && !following) return { title: 'Your edits here are not reaching anyone', text: st.readOnly.why }
|
|
86
|
+
if (!s) return null
|
|
87
|
+
if (s.problem || s.opening) return { title: s.problem ? 'Not following its square yet' : 'Starting to follow its square', text: s.line }
|
|
88
|
+
// Following its square with nothing to settle: the square button at the top says it, so no card (Daniel, 2026-11-01).
|
|
89
|
+
if (!(s.conflicted ?? (s.brought && s.brought.conflicts ? s.brought.conflicts.length : 0))) return null
|
|
90
|
+
return { title: 'Main conflicts with this square', text: catchUpWords(s).join(' ') }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The source control panel's top at layout 3, in place of the branch list: the square, its line, and
|
|
95
|
+
* the two things a person does with it, Commit (the square's live work, with a message) and Merge onto
|
|
96
|
+
* main (Seam merges once its checks pass; the folder carries on in a new square). `busy`: 'commit' or
|
|
97
|
+
* 'merge' while that one runs. A viewer, or a folder not following its square yet, gets the line only.
|
|
98
|
+
*/
|
|
99
|
+
export function squarePanelHtml (st, { busy = '' } = {}) {
|
|
100
|
+
const s = st.square
|
|
101
|
+
const name = squareName(st)
|
|
102
|
+
const following = s && !s.opening && !s.problem
|
|
103
|
+
const viewer = !!(st.access && st.access.role === 'viewer')
|
|
104
|
+
const head = `<div class="br-here sq-here"><div class="br-title">${name ? `Square <b>${esc(name)}</b> of ${esc(quiltName(st))}` : `Code committed and merged in Seam: <b>${esc(quiltName(st))}</b>`}</div>`
|
|
105
|
+
// While the session moves, nothing is committed or merged from here: the move is what it waits for.
|
|
106
|
+
if (st.moving) return `${head}<div class="br-sub warn">${esc(movingCard(st).text)}</div></div>`
|
|
107
|
+
if (st.squareless) {
|
|
108
|
+
const w = squarelessWords(st)
|
|
109
|
+
return `${head}<div class="br-sub warn">${esc(w.text)}</div>${viewer ? '' : `<div class="sq-acts">${openSquareButton(st, busy)}</div>`}</div>`
|
|
110
|
+
}
|
|
111
|
+
if (!following) {
|
|
112
|
+
const why = st.readOnly ? st.readOnly.why : s ? s.line : 'This folder is not following a square yet: Quilt opens it by itself once the session says which.'
|
|
113
|
+
return `${head}<div class="br-sub${st.readOnly || (s && s.problem) ? ' warn' : ''}">${esc(why)}</div></div>`
|
|
114
|
+
}
|
|
115
|
+
const sub = '<div class="br-sub">Every edit here is live for everyone on this square. Commit it to keep a version; merge it to put it on main (GitHub gets main from Seam by itself).</div>'
|
|
116
|
+
const caught = catchUpWords(s)
|
|
117
|
+
const work = (caught.length ? `<div class="br-sub${s.conflicted ? ' warn' : ''}">${esc(caught.join(' '))}</div>` : '') + workHtml(s.work)
|
|
118
|
+
if (viewer) return `${head}${sub}${work}</div>`
|
|
119
|
+
return `${head}${sub}${work}
|
|
120
|
+
<form class="cp-commit" data-square-commit><input class="input" name="message" placeholder="What this commit is" aria-label="Commit message" maxlength="2000" required autocomplete="off"${busy ? ' disabled' : ''}><button type="submit" class="btn sm primary"${busy ? ' disabled' : ''}>${busy === 'commit' ? 'Committing…' : 'Commit'}</button></form>
|
|
121
|
+
<div class="sq-acts"><button type="button" class="btn sm" data-square-merge${busy ? ' disabled' : ''} title="Commits what is not committed yet, then merges ${esc(name)} onto main once its checks pass">${busy === 'merge' ? 'Merging…' : 'Merge onto main'}</button></div>
|
|
122
|
+
</div>`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** While the owner moves the session (status `moving`, the relay's): { title, text }, what to expect in plain words. */
|
|
126
|
+
export function movingCard (st) {
|
|
127
|
+
const m = st.moving || {}
|
|
128
|
+
return { title: m.kind === 'back' ? 'Moving the code back to git' : 'Moving the code into Seam', text: m.line || (m.kind === 'back' ? 'The session\'s code is moving back from Seam to its git branches. Keep Quilt open: it can take a few minutes, and your edits are kept.' : 'The session\'s code is moving into Seam, where it will be committed and merged; the session stays in Quilt. Keep Quilt open: it can take a few minutes, and your edits are kept.') }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A folder whose branch has no square in the quilt (made after the move into Seam, or main with nothing
|
|
133
|
+
* to adopt): its edits reach nobody until a square is opened for it. { title, text } in plain words.
|
|
134
|
+
*/
|
|
135
|
+
export function squarelessWords (st) {
|
|
136
|
+
const b = (st.squareless && st.squareless.branch) || 'this branch'
|
|
137
|
+
return { title: `${b} has no square yet`, text: `Your folder is on ${b}, which has no square in Seam, so your edits here reach nobody. Open a square for it: your files stay as they are, they become its work, and everyone on ${b} follows it.` }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The button that opens a square for the folder's branch (squareTool open_square). */
|
|
141
|
+
export function openSquareButton (st, busy = '') {
|
|
142
|
+
const b = (st.squareless && st.squareless.branch) || 'this branch'
|
|
143
|
+
return `<button type="button" class="btn sm primary" data-square-open${busy ? ' disabled' : ''}>${busy === 'open' ? 'Opening…' : `Open a square for ${esc(b)}`}</button>`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** What the square holds not committed yet (everyone's on it), folded: what Commit takes. */
|
|
147
|
+
function workHtml (work) {
|
|
148
|
+
if (!work) return ''
|
|
149
|
+
if (!work.count) return '<div class="br-sub">Nothing here is waiting to be committed.</div>'
|
|
150
|
+
const word = { added: 'new', deleted: 'deleted', modified: '' }
|
|
151
|
+
const more = work.count - work.files.length
|
|
152
|
+
return `<details class="sq-work"><summary>${files(work.count)} not committed yet</summary><ul class="commit-files">${work.files.map((f) => `<li><code title="${esc(f.path)}">${esc(f.path)}</code>${word[f.status] ? ` <span class="hint">${word[f.status]}</span>` : ''}</li>`).join('')}${more > 0 ? `<li class="hint">and ${more} more</li>` : ''}</ul></details>`
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The words on the owner's "Make this a quilt" card (16 flag 9), while the session's code is on git.
|
|
157
|
+
* `quilt`: the status's quilt link ({ slug, shadow }), `remote`: the session has a git remote.
|
|
158
|
+
*/
|
|
159
|
+
export function makeQuiltWords ({ quilt = null, remote = false, moving = false, force = null, importing = null, shadow = null } = {}) {
|
|
160
|
+
const w = makeQuiltWordsNow({ quilt, remote, moving, importing, shadow })
|
|
161
|
+
// The forced date (16 flag 9): live sync stopped here, or when it comes (force-date.js).
|
|
162
|
+
if (force && force.stopped) return { ...w, note: force.stopped.why || '' }
|
|
163
|
+
const ahead = force && force.at ? forceAheadWords({ at: Date.parse(force.at), remote: !!force.remote }) : null
|
|
164
|
+
return ahead ? { ...w, note: ahead } : w
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function makeQuiltWordsNow ({ quilt, remote, moving, importing, shadow }) {
|
|
168
|
+
if (moving) return { text: 'The session\'s code is moving into Seam now. Keep Quilt open: your edits are kept.', button: 'Moving into Seam…', disabled: true }
|
|
169
|
+
// A session with a git remote, step by step (quilt-remote.js): Seam importing it, then linked and ready for the cut-over.
|
|
170
|
+
if (shadow || (quilt && quilt.shadow)) return { text: `Seam follows this session's git remote in the quilt ${(shadow || quilt).slug || ''}. When the team is ready: the repository's GitHub owner lets only Seam push to main (the steps show when you click), then move the session in. Its branches become squares, its uncommitted work a square of its own, and GitHub keeps getting main from Seam.`, button: 'Move into Seam now' }
|
|
171
|
+
if (importing) return { text: `Seam is importing ${importing.repo} into the quilt ${importing.slug} and follows it. Git stays as it is meanwhile: remote, clones and CI. Check again once it has caught up.`, button: 'Check the import' }
|
|
172
|
+
if (quilt && !quilt.shadow) return { text: `This session is the quilt ${quilt.slug} already, but its files are still on its branches. Finish the move to put them in its squares.`, button: 'Finish the move' }
|
|
173
|
+
if (remote) return { text: 'This session has a git remote, so it becomes a quilt with its git history: Seam imports the remote and follows it, then the session moves in when the import is ready. Your git remote, clones and CI keep working.', button: 'Make this a quilt' }
|
|
174
|
+
return { text: 'Move this session\'s code into Seam: main as it is now becomes the quilt\'s first commit, each branch a square, and everyone\'s folder follows its square live. Nothing is lost: the session can go back to its branches.', button: 'Make this a quilt' }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Session settings' Source control at layout 3, for everyone in it: where the code's history is, in
|
|
179
|
+
* place of the git and GitHub settings, which are put away while the session is in Seam (the relay
|
|
180
|
+
* keeps them for a move back: quilt session migrate --back).
|
|
181
|
+
*/
|
|
182
|
+
export function seamSettingsHtml (st) {
|
|
183
|
+
return `<div class="pm-section"><div class="pm-title">Seam</div><div class="pm-card git-card">
|
|
184
|
+
<div class="git-head"><h4>This session's source control</h4><span class="pill">Seam</span></div>
|
|
185
|
+
<p class="git-line ok"><b>${esc(quiltName(st))}.</b> This session's code is committed and merged in Seam; the session itself is Quilt. Everyone's folder follows its branch's square: people and their AIs commit there and merge onto main, and GitHub gets main from Seam by itself.</p>
|
|
186
|
+
<span class="hint">Git and GitHub settings are put away while the session's code is in Seam. If its owner moves it back to its branches (quilt session migrate --back), they come back as they were.</span>
|
|
187
|
+
</div></div>`
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The left sidebar in Seam, in place of boxes (there are none: every edit is live in its square): each
|
|
192
|
+
* square with whose folders are on it, and, for yours, how many files it holds not committed yet.
|
|
193
|
+
* `st.squares` is branch -> square name; people are you (`st.square`) and `st.peers` by their branch.
|
|
194
|
+
*/
|
|
195
|
+
export function squaresSideHtml (st) {
|
|
196
|
+
const squares = st.squares || {}
|
|
197
|
+
const branchOf = Object.fromEntries(Object.entries(squares).map(([b, sq]) => [sq, b]))
|
|
198
|
+
const groups = new Map()
|
|
199
|
+
const add = (sq, who) => { if (!sq) return; if (!groups.has(sq)) groups.set(sq, []); if (!groups.get(sq).includes(who)) groups.get(sq).push(who) }
|
|
200
|
+
const mine = squareName(st)
|
|
201
|
+
add(mine, 'you')
|
|
202
|
+
for (const p of st.peers || []) if (p && p.name && p.branch && squares[p.branch] && !(p.persona && p.of === (st.me && st.me.name))) add(squares[p.branch], p.mine ? 'your AI' : p.name)
|
|
203
|
+
const n = st.square && st.square.work ? st.square.work.count : 0
|
|
204
|
+
const rows = [...groups].map(([sq, who]) => `<li class="sq-side-row${sq === mine ? ' on' : ''}"><div><b>${esc(sq)}</b>${branchOf[sq] ? ` <span class="hint">${esc(branchOf[sq])}</span>` : ''}</div><div class="hint">${esc(who.join(', '))}${sq === mine ? ` · ${n ? `${files(n)} not committed` : 'all committed'}` : ''}</div></li>`).join('')
|
|
205
|
+
// The pane's own title says Squares already (session.js, as it says Boxes outside Seam): no second heading.
|
|
206
|
+
return rows ? `<ul class="sq-side">${rows}</ul>` : ''
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The sidebar's line under it in Seam. */
|
|
210
|
+
export const SEAM_SIDE_HINT = 'In Seam nothing waits in boxes: everyone on a square sees every edit as it is made. Commit and merge your square from the square button at the top.'
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* What the square panel's Commit or Merge onto main did, for a person: from the Seam tool's own record
|
|
214
|
+
* (`structuredContent`), never its tool text (ids, `held by person:…`, CLI hints). `text` is the tool's
|
|
215
|
+
* text, read only for what has no record: nothing to commit, the folder carrying on in a new square.
|
|
216
|
+
*/
|
|
217
|
+
export function seamResultWords (tool, { structuredContent: rec = null, text = '' } = {}) {
|
|
218
|
+
const plural = (n, w) => `${n} ${w}${n === 1 ? '' : 's'}`
|
|
219
|
+
const names = (list) => list.length > 4 ? `${list.slice(0, 4).join(', ')} and ${list.length - 4} more` : list.join(', ')
|
|
220
|
+
const careful = (rec && rec.warnings || []).map((w) => w.kind === 'markers_in_file' ? `${w.path} still has conflict markers in it.` : `${w.path} looks like it holds a secret: check it before you merge.`)
|
|
221
|
+
if (tool === 'seam_commit') {
|
|
222
|
+
if (/^Nothing to commit/.test(text)) return 'Nothing to commit: everything in this square is committed already.'
|
|
223
|
+
const c = rec && rec.result
|
|
224
|
+
if (!c) return text
|
|
225
|
+
const n = Array.isArray(c.changes) ? c.changes.length : 0
|
|
226
|
+
return [`Committed "${String(c.message || '').split('\n')[0]}"${n ? `: ${plural(n, 'file')}` : ''}.`, ...careful].join(' ')
|
|
227
|
+
}
|
|
228
|
+
if (tool === 'seam_merge') {
|
|
229
|
+
if (!rec) return text
|
|
230
|
+
const moved = /carries on in a new square/.test(text) ? 'Your folder carries on in a new square on main\'s new version: keep working as before.' : ''
|
|
231
|
+
if (rec.ok && rec.merged) {
|
|
232
|
+
const m = rec.merged
|
|
233
|
+
const combined = (rec.combined || []).map((c) => c.path)
|
|
234
|
+
return [`Merged onto ${m.line || 'main'} as patch ${m.patch_no}${m.subject ? ` "${m.subject}"` : ''}.`, combined.length ? `Combined with changes already on ${m.line || 'main'} in ${names(combined)}.` : '', ...careful, moved].filter(Boolean).join(' ')
|
|
235
|
+
}
|
|
236
|
+
const terms = (rec.terms || []).map((t) => t.path)
|
|
237
|
+
const why = rec.refused ? rec.refused.detail : rec.error || 'Seam refused'
|
|
238
|
+
return [`Not merged: ${why}.`, terms.length ? `${plural(terms.length, 'file')} ${terms.length === 1 ? 'conflicts' : 'conflict'} with main and ${terms.length === 1 ? 'is' : 'are'} marked in your folder (${names(terms)}): settle ${terms.length === 1 ? 'it' : 'them'}, commit, and merge again.` : ''].filter(Boolean).join(' ')
|
|
239
|
+
}
|
|
240
|
+
return text
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The words for a session that stopped by itself (the relay sent this app away, its owner ended it, ...):
|
|
245
|
+
* `session` the app's record of it (for its name), `why` what Quilt was told.
|
|
246
|
+
*/
|
|
247
|
+
export function stoppedWords (session, why) {
|
|
248
|
+
const st = session && session.status
|
|
249
|
+
const name = (st && st.sessionName) || (session && session.dir ? String(session.dir).split(/[\\/]/).filter(Boolean).pop() : '') || 'The session'
|
|
250
|
+
return `${name} stopped: ${String(why || '').replace(/^Relay refused connection: /, '')}`
|
|
251
|
+
}
|