clavarium-binder 0.1.3 → 0.1.5-next.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/bin/binder.mjs +9 -3
- package/host/binder-host.mjs +21 -2
- package/package.json +1 -1
- package/server/binder.mjs +2 -2
- package/server/store.mjs +44 -4
- package/ui/binder-ui.js +9 -3
- package/ui/texts/en.json +2 -0
- package/ui/texts/fr.json +2 -0
package/bin/binder.mjs
CHANGED
|
@@ -20,7 +20,7 @@ export const HELP = `binder: the lists you were given, and their records.
|
|
|
20
20
|
[--cover PICTURE|none] the picture the card is shown by: one attached (its ref), or a photo field
|
|
21
21
|
binder move ID STATE past a gate, only what a person approved
|
|
22
22
|
binder note ID "text" a short note: a decision, a fact, a result
|
|
23
|
-
binder attach ID KIND REF ["title"] KIND: message, file, asset, branch, link, record
|
|
23
|
+
binder attach ID KIND REF ["title"] KIND: message, file, asset, branch, commit, link, record
|
|
24
24
|
binder detach ID WHAT
|
|
25
25
|
binder archive ID | binder restore ID
|
|
26
26
|
binder new TEMPLATE ["title"] a list of your own, when none you were given fits
|
|
@@ -36,7 +36,7 @@ You never approve: a person ticks what may go on. Talking with them stays in the
|
|
|
36
36
|
The folder used is --dir FOLDER, else $BINDER_DIR, else the nearest ${FOLDER} folder from here up.
|
|
37
37
|
binder ${VERSION}`;
|
|
38
38
|
|
|
39
|
-
const VERBS = ['lists', 'show', 'get', 'search', 'add', 'edit', 'move', 'note', 'attach', 'detach', 'archive', 'restore', 'new', 'archive-list', 'restore-list'];
|
|
39
|
+
const VERBS = ['lists', 'show', 'get', 'search', 'add', 'edit', 'move', 'note', 'attach', 'detach', 'archive', 'restore', 'new', 'archive-list', 'restore-list', 'columns'];
|
|
40
40
|
|
|
41
41
|
/* A command line, read: [op, args] or a refusal. */
|
|
42
42
|
export function read(argv) {
|
|
@@ -68,7 +68,13 @@ export function read(argv) {
|
|
|
68
68
|
case 'detach': return rest.length === 2 ? ['detach', { id: rest[0], what: rest[1] }] : { refused: 'binder detach ID WHAT' };
|
|
69
69
|
case 'new': return rest[0] ? ['create-list', { template: rest[0], title: rest.slice(1).join(' ') || undefined }] : { refused: 'binder new TEMPLATE ["title"]' };
|
|
70
70
|
case 'archive-list': case 'restore-list': return rest[0] ? ['archive-list', { list: rest[0], archived: verb === 'archive-list' }] : { refused: `binder ${verb} LIST` };
|
|
71
|
-
case '
|
|
71
|
+
case 'columns': {
|
|
72
|
+
const [list, action, ...more] = rest;
|
|
73
|
+
if (!list || !action) return { refused: 'binder columns LIST add "Name" [--after STATE|--before STATE] | binder columns LIST rename "Old" "New"' };
|
|
74
|
+
if (action === 'add') return more.length ? ['columns', { list, add: more.join(' '), ...(flags.after ? { after: flags.after } : {}), ...(flags.before ? { before: flags.before } : {}) }] : { refused: 'binder columns LIST add "Name"' };
|
|
75
|
+
if (action === 'rename') return more.length >= 2 ? ['columns', { list, rename: { from: more[0], to: more.slice(1).join(' ') } }] : { refused: 'binder columns LIST rename "Old" "New"' };
|
|
76
|
+
return { refused: 'binder columns LIST add "Name" | rename "Old" "New"' };
|
|
77
|
+
}
|
|
72
78
|
default: return { refused: 'not understood' };
|
|
73
79
|
}
|
|
74
80
|
}
|
package/host/binder-host.mjs
CHANGED
|
@@ -71,7 +71,8 @@ export function say(op, data, list) {
|
|
|
71
71
|
case 'attach': return 'Attached.';
|
|
72
72
|
case 'detach': return 'Taken off.';
|
|
73
73
|
case 'archive': return data.archived ? `Archived #${data.id}.` : `Restored #${data.id}.`;
|
|
74
|
-
case 'settings': case 'edit-settings': return `Lists an agent may make: ${data.agentLists}.`;
|
|
74
|
+
case 'settings': case 'edit-settings': return `Lists an agent may make: ${data.agentLists}. Columns an agent may change: ${data.agentColumns ? 'yes' : 'no'}.`;
|
|
75
|
+
case 'edit-list': case 'columns': return `The columns are set: ${(data.states ?? []).map((x) => x.name).join(', ')}.`;
|
|
75
76
|
case 'create-list': return `Made the list ${data.title} [${data.id}], yours to change: binder add ${data.id} "title".`;
|
|
76
77
|
case 'archive-list': return data.archived ? `Archived the list ${data.title} [${data.id}]. A person can restore or delete it.` : `Restored the list ${data.title} [${data.id}].`;
|
|
77
78
|
default: return 'Done.';
|
|
@@ -104,11 +105,29 @@ export function keeper(dir, { sides = () => [], now = () => Date.now() } = {}) {
|
|
|
104
105
|
case 'edit-note': data = store.editNote(a.id, a.note, a.text, who); break;
|
|
105
106
|
case 'create-list': data = store.createList({ template: a.template, title: a.title, lang: a.lang }, who); break;
|
|
106
107
|
case 'edit-list': data = store.editList(a.list, { title: a.title, view: a.view, states: a.states, fields: a.fields }, who); break;
|
|
108
|
+
case 'columns': {
|
|
109
|
+
/* Add or rename a column, keeping every other column (its id, its gate, its "done") as it is,
|
|
110
|
+
then edit the list with the whole set -- so the gate is never worked around. */
|
|
111
|
+
const match = (ref) => (x) => x.id === ref || x.name.toLowerCase() === String(ref ?? '').toLowerCase();
|
|
112
|
+
const l = store.lists(who).find(match(a.list));
|
|
113
|
+
if (!l) throw new Refused(`no list ${oneLine(a.list, 100) || '(none given)'}`);
|
|
114
|
+
const states = l.states.map((x) => ({ id: x.id, name: x.name, ...(x.gate ? { gate: true } : {}), ...(x.done ? { done: true } : {}) }));
|
|
115
|
+
if (a.rename) { const c = states.find(match(a.rename.from)); if (!c) throw new Refused(`no column "${a.rename.from}"`); c.name = oneLine(a.rename.to, 60); }
|
|
116
|
+
if (a.add) {
|
|
117
|
+
const name = oneLine(a.add, 60); if (!name) throw new Refused('a column needs a name');
|
|
118
|
+
let at = states.length;
|
|
119
|
+
if (a.after !== undefined) { const i = states.findIndex(match(a.after)); if (i >= 0) at = i + 1; }
|
|
120
|
+
else if (a.before !== undefined) { const i = states.findIndex(match(a.before)); if (i >= 0) at = i; }
|
|
121
|
+
states.splice(at, 0, { name });
|
|
122
|
+
}
|
|
123
|
+
data = store.editList(a.list, { states }, who);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
107
126
|
case 'grant': data = store.grant(a.list, a.to, a.access ?? null, who); break;
|
|
108
127
|
case 'archive-list': data = store.archiveList(a.list, a.archived !== false, who); break;
|
|
109
128
|
case 'delete-list': data = store.deleteList(a.list, who); break;
|
|
110
129
|
case 'settings': data = store.settings(who); break;
|
|
111
|
-
case 'edit-settings': data = store.editSettings({ agentLists: a.agentLists }, who); break;
|
|
130
|
+
case 'edit-settings': data = store.editSettings({ agentLists: a.agentLists, agentColumns: a.agentColumns }, who); break;
|
|
112
131
|
case 'templates': data = Object.fromEntries(Object.entries(TEMPLATES).map(([k, t]) => [k, t.name])); break;
|
|
113
132
|
default: return { ok: false, text: `there is no ${oneLine(op, 40)}` };
|
|
114
133
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clavarium-binder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5-next.1",
|
|
4
4
|
"description": "Clavarium Binder: lists of records kept together by people and AI agents, each with its id (a command and an MCP server for agents, a keeper and a screen for the application). No dependency.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/clavarium/clavarium-binder#readme",
|
package/server/binder.mjs
CHANGED
|
@@ -23,7 +23,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
23
23
|
import { randomUUID } from 'node:crypto';
|
|
24
24
|
import { createInterface } from 'node:readline';
|
|
25
25
|
|
|
26
|
-
export const VERSION = '0.1.
|
|
26
|
+
export const VERSION = '0.1.5-next.1';
|
|
27
27
|
export const FOLDER = '.binder';
|
|
28
28
|
/* How long an ask waits for its answer. The keeper answers within a second or two. */
|
|
29
29
|
export const WAIT_MS = 20_000;
|
|
@@ -71,7 +71,7 @@ export const TOOLS = [
|
|
|
71
71
|
{ name: 'edit', description: 'Change a record: title, description, fields, owners, due date, lead reminder, cover picture (empty to remove it).', inputSchema: { type: 'object', properties: { id: { type: 'string' }, title: { type: 'string' }, description: DESCRIBE, due: { type: 'string' }, remindBefore: { type: 'integer', description: 'remind the owners this many days before the due time (e.g. 1, 3, 7); 0 removes it' }, owner: S('"me", or an id; empty for nobody'), fields: { type: 'object' }, cover: S('the picture the card is shown by, when it has several: a picture attached (its ref) or a photo field; empty to remove it') }, required: ['id'] } },
|
|
72
72
|
{ name: 'move', description: 'Put a record in another state of its list. Past a gate (an approval), only once a person approved it.', inputSchema: { type: 'object', properties: { id: { type: 'string' }, state: { type: 'string' } }, required: ['id', 'state'] } },
|
|
73
73
|
{ name: 'note', description: 'Add a short note to a record: a decision, a fact, a result. Talking with the person stays in the chat.', inputSchema: { type: 'object', properties: { id: { type: 'string' }, text: { type: 'string' } }, required: ['id', 'text'] } },
|
|
74
|
-
{ name: 'attach', description: 'Attach something to a record: a message of the chat, a file, an asset, a branch, a link, another record.', inputSchema: { type: 'object', properties: { id: { type: 'string' }, kind: { type: 'string', enum: ['message', 'file', 'asset', 'branch', 'link', 'record'] }, ref: S('what it is: a path, a URL, repository:branch, a message id, a record number'), title: { type: 'string' } }, required: ['id', 'kind', 'ref'] } },
|
|
74
|
+
{ name: 'attach', description: 'Attach something to a record: a message of the chat, a file, an asset, a branch, a commit, a link, another record.', inputSchema: { type: 'object', properties: { id: { type: 'string' }, kind: { type: 'string', enum: ['message', 'file', 'asset', 'branch', 'commit', 'link', 'record'] }, ref: S('what it is: a path, a URL, repository:branch, repository:sha, a message id, a record number'), title: { type: 'string' } }, required: ['id', 'kind', 'ref'] } },
|
|
75
75
|
{ name: 'detach', description: 'Take back something attached to a record.', inputSchema: { type: 'object', properties: { id: { type: 'string' }, what: S('what get shows of it: its ref or its id') }, required: ['id', 'what'] } },
|
|
76
76
|
{ name: 'new-list', description: 'Make a list from a template (batches, development, customers, people, roadmap, simple) when none you were given fits: it is given to you alone, and the person sees it. Batches: the work you propose; the person ticks what you may start.', inputSchema: { type: 'object', properties: { template: S('batches, development, customers, people, roadmap or simple'), title: S('its name; the template\'s by default') }, required: ['template'] } },
|
|
77
77
|
{ name: 'archive-list', description: 'Archive a list you made (it leaves your tabs; a person still sees it and can restore or delete it), or restore it. You cannot delete a list; only a person does. Archived lists do not count against your few-lists limit.', inputSchema: { type: 'object', properties: { list: S('its name or id'), restore: { type: 'boolean' } }, required: ['list'] } },
|
package/server/store.mjs
CHANGED
|
@@ -31,6 +31,10 @@ export const TITLE_MAX = 200;
|
|
|
31
31
|
/* Lists an agent may have made and not archived: 5 until a person sets it (settings.agentLists), 0 to 50. */
|
|
32
32
|
export const AGENT_LISTS_DEFAULT = 5;
|
|
33
33
|
export const AGENT_LISTS_TOP = 50;
|
|
34
|
+
/* Whether an agent may change a list's columns (its states): true until a person turns it off
|
|
35
|
+
(settings.agentColumns). Even on, an agent never touches the gate -- the person's approval
|
|
36
|
+
boundary -- nor removes a column: it only adds and renames, so a gate can never be worked around. */
|
|
37
|
+
export const AGENT_COLUMNS_DEFAULT = true;
|
|
34
38
|
/* The default, by its former name. */
|
|
35
39
|
export const AGENT_LISTS_MAX = AGENT_LISTS_DEFAULT;
|
|
36
40
|
export const NOTE_MAX = 2000;
|
|
@@ -45,7 +49,7 @@ export const ATTACHED_MAX = 200;
|
|
|
45
49
|
export const STATES_MAX = 12;
|
|
46
50
|
export const FIELDS_MAX = 20;
|
|
47
51
|
export const OWNERS_MAX = 10;
|
|
48
|
-
export const ATTACH_KINDS = ['message', 'file', 'asset', 'branch', 'link', 'record'];
|
|
52
|
+
export const ATTACH_KINDS = ['message', 'file', 'asset', 'branch', 'commit', 'link', 'record'];
|
|
49
53
|
/* A file attached that is a picture, by its name (the screen has the same rule). */
|
|
50
54
|
export const isPicture = (ref) => /\.(jpe?g|png|webp|gif)$/i.test(String(ref ?? '').split(/[?#]/)[0]);
|
|
51
55
|
|
|
@@ -57,6 +61,33 @@ const key = (text) => oneLine(text, 40).toLowerCase().normalize('NFD').replace(/
|
|
|
57
61
|
export class Refused extends Error {}
|
|
58
62
|
const no = (why) => { throw new Refused(why); };
|
|
59
63
|
|
|
64
|
+
/* An agent changing a list's columns (its states) may ADD new ones and RENAME existing ones, but
|
|
65
|
+
never remove a column, never add/remove/move the gate, and never move an existing column across
|
|
66
|
+
the gate. So the gate -- the person's approval boundary -- can never be worked around. Throws
|
|
67
|
+
(Refused) when a proposed set of states would break any of that. `have` is the list's states now. */
|
|
68
|
+
function agentColumnsOk(have, proposed) {
|
|
69
|
+
if (!Array.isArray(proposed) || proposed.length < 1) no('say the columns');
|
|
70
|
+
const idOf = (st) => oneLine(st?.id, 40) || key(oneLine(st?.name, 60)) || 'state';
|
|
71
|
+
const propIds = proposed.map(idOf);
|
|
72
|
+
const propSet = new Set(propIds);
|
|
73
|
+
for (const st of have) if (!propSet.has(st.id)) no('an agent adds or renames columns, it does not remove one -- ask a person');
|
|
74
|
+
proposed.forEach((st, i) => {
|
|
75
|
+
const existing = have.find((h) => h.id === propIds[i]);
|
|
76
|
+
if (existing) { if (!!st?.gate !== !!existing.gate || !!st?.done !== !!existing.done) no('an agent does not change a column\'s gate or its "done" -- ask a person'); }
|
|
77
|
+
else if (st?.gate || st?.done) no('an agent does not add a gate or a "done" column -- ask a person');
|
|
78
|
+
});
|
|
79
|
+
const gate = have.find((x) => x.gate);
|
|
80
|
+
if (gate) {
|
|
81
|
+
const gatePos = propIds.indexOf(gate.id);
|
|
82
|
+
const haveGatePos = have.findIndex((x) => x.id === gate.id);
|
|
83
|
+
for (const st of have) {
|
|
84
|
+
if (st.id === gate.id) continue;
|
|
85
|
+
const wasBefore = have.findIndex((x) => x.id === st.id) < haveGatePos;
|
|
86
|
+
if (wasBefore !== (propIds.indexOf(st.id) < gatePos)) no('an agent does not move a column across the gate -- ask a person');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
60
91
|
/* A due date: "2026-11-04" or "2026-11-04 09:00", kept as written (the keeper's clock reads it). */
|
|
61
92
|
const DUE = /^\d{4}-\d{2}-\d{2}( \d{2}:\d{2})?$/;
|
|
62
93
|
export const dueAt = (due) => { const t = Date.parse(due.length === 10 ? `${due}T09:00:00` : due.replace(' ', 'T')); return Number.isFinite(t) ? t : null; };
|
|
@@ -125,7 +156,7 @@ export function openStore(dir, { now = () => Date.now() } = {}) {
|
|
|
125
156
|
|
|
126
157
|
/* Whether a record's title, description or notes hold `q` (lower case). */
|
|
127
158
|
/* The store's settings, as they are now (a store from before them has the defaults). */
|
|
128
|
-
const settingsNow = () => ({ agentLists: Number.isInteger(s.settings?.agentLists) ? s.settings.agentLists : AGENT_LISTS_DEFAULT });
|
|
159
|
+
const settingsNow = () => ({ agentLists: Number.isInteger(s.settings?.agentLists) ? s.settings.agentLists : AGENT_LISTS_DEFAULT, agentColumns: typeof s.settings?.agentColumns === 'boolean' ? s.settings.agentColumns : AGENT_COLUMNS_DEFAULT });
|
|
129
160
|
|
|
130
161
|
const said = (r, q) => r.title.toLowerCase().includes(q) || (r.description ?? '').toLowerCase().includes(q) || r.notes.some((n) => n.text.toLowerCase().includes(q));
|
|
131
162
|
|
|
@@ -214,8 +245,13 @@ export function openStore(dir, { now = () => Date.now() } = {}) {
|
|
|
214
245
|
},
|
|
215
246
|
editList(given, { title, view, states, fields } = {}, who) {
|
|
216
247
|
const w = whoOf(who);
|
|
217
|
-
if (!isPerson(w))
|
|
248
|
+
if (!isPerson(w)) {
|
|
249
|
+
/* An agent may change only the columns (states), when a person allows it, and never the gate. */
|
|
250
|
+
if (title !== undefined || view !== undefined || fields !== undefined || states === undefined) no('only a person changes a list (an agent may change its columns)');
|
|
251
|
+
if (!settingsNow().agentColumns) no('a person turned off the columns an agent may change');
|
|
252
|
+
}
|
|
218
253
|
const list = listOf(given, w, 'write');
|
|
254
|
+
if (!isPerson(w)) agentColumnsOk(list.states, states);
|
|
219
255
|
const change = {};
|
|
220
256
|
if (title !== undefined) { const t = oneLine(title, TITLE_MAX); if (!t) no('a list needs a title'); list.title = change.title = t; }
|
|
221
257
|
if (view !== undefined) { if (!VIEWS.includes(view)) no(`a view is one of: ${VIEWS.join(', ')}`); list.view = change.view = view; }
|
|
@@ -280,7 +316,7 @@ export function openStore(dir, { now = () => Date.now() } = {}) {
|
|
|
280
316
|
|
|
281
317
|
/* ---- the store's settings: anyone reads them, a person changes them ---- */
|
|
282
318
|
settings(who) { whoOf(who); return settingsNow(); },
|
|
283
|
-
editSettings({ agentLists } = {}, who) {
|
|
319
|
+
editSettings({ agentLists, agentColumns } = {}, who) {
|
|
284
320
|
const w = whoOf(who);
|
|
285
321
|
if (!isPerson(w)) no('only a person changes the settings');
|
|
286
322
|
const change = {};
|
|
@@ -289,6 +325,10 @@ export function openStore(dir, { now = () => Date.now() } = {}) {
|
|
|
289
325
|
if (!Number.isInteger(n) || n < 0 || n > AGENT_LISTS_TOP) no(`lists an agent may make: a whole number from 0 to ${AGENT_LISTS_TOP}`);
|
|
290
326
|
change.agentLists = n;
|
|
291
327
|
}
|
|
328
|
+
if (agentColumns !== undefined) {
|
|
329
|
+
const b = agentColumns === true || agentColumns === 'true' ? true : agentColumns === false || agentColumns === 'false' ? false : no('columns an agent may change: true or false');
|
|
330
|
+
change.agentColumns = b;
|
|
331
|
+
}
|
|
292
332
|
if (!Object.keys(change).length) return settingsNow();
|
|
293
333
|
s.settings = { ...s.settings, ...change };
|
|
294
334
|
save(); log(w, 'edit-settings', { change });
|
package/ui/binder-ui.js
CHANGED
|
@@ -29,10 +29,10 @@ export const KEYS = Object.freeze(['lists', 'binder', 'new-list', 'new-item', 'n
|
|
|
29
29
|
'states', 'fields', 'access', 'read', 'write', 'none', 'save', 'empty', 'archived', 'show-archived', 'gate', 'done', 'add-state', 'add-field',
|
|
30
30
|
'kind-message', 'kind-file', 'kind-asset', 'kind-branch', 'kind-link', 'kind-record', 'what', 'name', 'type', 'saved', 'edited', 'by', 'mine', 'all',
|
|
31
31
|
'delete-list', 'delete-list-sure', 'archive-list', 'no-records', 'none-archived', 'none-mine', 'search', 'description', 'edit', 'more', 'approve', 'add', 'ok',
|
|
32
|
-
'add-what', 'owner', 'attachment', 'tools', 'cover', 'agent-lists', 'remind-before', 'no-remind', 'days',
|
|
32
|
+
'add-what', 'owner', 'attachment', 'tools', 'cover', 'agent-lists', 'agent-columns', 'remind-before', 'no-remind', 'days',
|
|
33
33
|
'op-add', 'op-edit', 'op-move', 'op-note', 'op-edit-note', 'op-remove-note', 'op-attach', 'op-detach', 'op-archive', 'op-restore', 'op-delete']);
|
|
34
|
-
const KINDS = ['message', 'file', 'asset', 'branch', 'link', 'record'];
|
|
35
|
-
const ICON = { message: '💬', file: '📄', asset: '🗄', branch: '⑂', link: '🔗', record: '▣' };
|
|
34
|
+
const KINDS = ['message', 'file', 'asset', 'branch', 'commit', 'link', 'record'];
|
|
35
|
+
const ICON = { message: '💬', file: '📄', asset: '🗄', branch: '⑂', commit: '◉', link: '🔗', record: '▣' };
|
|
36
36
|
const TYPES = ['text', 'number', 'date', 'link', 'photo'];
|
|
37
37
|
const DESCRIPTION_MAX = 4000;
|
|
38
38
|
|
|
@@ -581,6 +581,11 @@ export function mount(root, { call, words = {}, lang = 'en', people = () => [],
|
|
|
581
581
|
n.addEventListener('change', () => void soft(ask('edit-settings', { agentLists: n.value })).then((got) => { if (got) ui.settings = got; paint(); }));
|
|
582
582
|
return n;
|
|
583
583
|
}
|
|
584
|
+
function agentColumns() {
|
|
585
|
+
const c = h('input', { type: 'checkbox', ...(ui.settings.agentColumns ? { checked: true } : {}), 'data-setting': 'agentColumns' });
|
|
586
|
+
c.addEventListener('change', () => void soft(ask('edit-settings', { agentColumns: c.checked })).then((got) => { if (got) ui.settings = got; paint(); }));
|
|
587
|
+
return h('label', { class: 'binder-toggle' }, c, h('span', {}, w('agent-columns')));
|
|
588
|
+
}
|
|
584
589
|
return h('div', { class: 'binder-page binder-settings' },
|
|
585
590
|
h('div', { class: 'binder-crumb' }, backButton(l.title)),
|
|
586
591
|
h('section', { class: 'binder-box' }, h('h3', {}, w('title')), title),
|
|
@@ -588,6 +593,7 @@ export function mount(root, { call, words = {}, lang = 'en', people = () => [],
|
|
|
588
593
|
h('section', { class: 'binder-box' }, h('h3', {}, w('fields')), fieldRows),
|
|
589
594
|
grants.length ? h('section', { class: 'binder-box' }, h('h3', {}, w('access')), grants) : null,
|
|
590
595
|
ui.settings ? h('section', { class: 'binder-box' }, h('h3', {}, w('agent-lists')), agentLists()) : null,
|
|
596
|
+
ui.settings ? h('section', { class: 'binder-box' }, h('h3', {}, w('agent-columns')), agentColumns()) : null,
|
|
591
597
|
h('div', { class: 'binder-actions' },
|
|
592
598
|
h('button', { type: 'button', class: 'binder-ghost', onclick: () => void soft(ask('archive-list', { list: l.id })).then(() => { ui.mode = 'list'; ui.at = null; return refresh(); }) }, w('archive-list')),
|
|
593
599
|
h('button', { type: 'button', class: 'binder-ghost', onclick: () => { sure.hidden = false; } }, w('delete-list')), sure,
|
package/ui/texts/en.json
CHANGED
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"kind-file": "File",
|
|
48
48
|
"kind-asset": "Asset",
|
|
49
49
|
"kind-branch": "Branch",
|
|
50
|
+
"kind-commit": "Commit",
|
|
50
51
|
"kind-link": "Link",
|
|
51
52
|
"kind-record": "Card",
|
|
52
53
|
"what": "What",
|
|
@@ -76,6 +77,7 @@
|
|
|
76
77
|
"tools": "More",
|
|
77
78
|
"cover": "Cover",
|
|
78
79
|
"agent-lists": "Lists an agent may make",
|
|
80
|
+
"agent-columns": "An agent may change the columns",
|
|
79
81
|
"op-add": "Created",
|
|
80
82
|
"op-edit": "Edited",
|
|
81
83
|
"op-move": "Moved",
|
package/ui/texts/fr.json
CHANGED
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"kind-file": "Fichier",
|
|
48
48
|
"kind-asset": "Asset",
|
|
49
49
|
"kind-branch": "Branche",
|
|
50
|
+
"kind-commit": "Commit",
|
|
50
51
|
"kind-link": "Lien",
|
|
51
52
|
"kind-record": "Fiche",
|
|
52
53
|
"what": "Quoi",
|
|
@@ -76,6 +77,7 @@
|
|
|
76
77
|
"tools": "Plus",
|
|
77
78
|
"cover": "À la une",
|
|
78
79
|
"agent-lists": "Classeurs qu'une IA peut créer",
|
|
80
|
+
"agent-columns": "Une IA peut changer les colonnes",
|
|
79
81
|
"op-add": "Créée",
|
|
80
82
|
"op-edit": "Modifiée",
|
|
81
83
|
"op-move": "Déplacée",
|