aimeat 1.23.2 → 1.23.3
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/dist/locales/en.json +536 -21
- package/dist/locales/fi.json +548 -33
- package/dist/public/components/ContactCard.js +28 -0
- package/dist/public/css/views/admin.css +12 -0
- package/dist/public/css/views/agents-detail.css +62 -10
- package/dist/public/css/views/landing.css +87 -0
- package/dist/public/css/views/offers.css +96 -0
- package/dist/public/css/views/portfolio.css +18 -0
- package/dist/public/css/views/profile.css +153 -8
- package/dist/public/css/views/start.css +96 -0
- package/dist/public/img/business-hero.png +0 -0
- package/dist/public/img/toimitus.png +0 -0
- package/dist/public/js/components/tag-cloud.js +46 -31
- package/dist/public/js/components/tag-editor.js +2 -2
- package/dist/public/js/recents.js +40 -0
- package/dist/public/js/services/admin.js +6 -0
- package/dist/public/js/services/agents.js +34 -0
- package/dist/public/js/services/offers.js +152 -0
- package/dist/public/js/services/packages.js +1 -0
- package/dist/public/spa.html +32 -2
- package/dist/public/views/admin/subdomains-tab.js +182 -0
- package/dist/public/views/admin.js +2 -0
- package/dist/public/views/business.js +119 -0
- package/dist/public/views/how-it-works.js +159 -0
- package/dist/public/views/landing.js +235 -0
- package/dist/public/views/portfolio.js +120 -23
- package/dist/public/views/pricing.js +75 -0
- package/dist/public/views/profile/access-tab.js +124 -51
- package/dist/public/views/profile/agents/agent-card.js +68 -37
- package/dist/public/views/profile/agents/shared-board.js +56 -22
- package/dist/public/views/profile/agents/tab-activity.js +24 -12
- package/dist/public/views/profile/agents/tab-agent-config.js +31 -1
- package/dist/public/views/profile/agents/tab-data-access.js +9 -13
- package/dist/public/views/profile/agents/tab-integration.js +27 -1
- package/dist/public/views/profile/agents/tab-messages.js +6 -11
- package/dist/public/views/profile/agents/tab-quality.js +45 -5
- package/dist/public/views/profile/agents-tab.js +41 -6
- package/dist/public/views/profile/apps-tab.js +7 -1
- package/dist/public/views/profile/knowledge-tab.js +4 -1
- package/dist/public/views/profile/landing-page.js +427 -123
- package/dist/public/views/profile/memory-tab.js +409 -211
- package/dist/public/views/profile/nodes-tab.js +18 -3
- package/dist/public/views/profile/offers-tab.js +310 -0
- package/dist/public/views/profile/organisms-tab.js +289 -28
- package/dist/public/views/profile/packages-tab.js +152 -16
- package/dist/public/views/profile/portfolio-tab.js +98 -31
- package/dist/public/views/profile.js +2 -0
- package/dist/public/views/start-flows.js +281 -0
- package/dist/public/views/start.js +258 -0
- package/dist/src/generated/api-types.d.ts +856 -17
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/generated/prisma-postgres/edge.js +15 -3
- package/dist/src/generated/prisma-postgres/index-browser.js +12 -0
- package/dist/src/generated/prisma-postgres/index.d.ts +1476 -160
- package/dist/src/generated/prisma-postgres/index.js +15 -3
- package/dist/src/generated/prisma-postgres/package.json +1 -1
- package/dist/src/generated/prisma-postgres/schema.prisma +11 -0
- package/dist/src/generated/prisma-postgres/wasm.js +15 -3
- package/dist/src/middleware/subdomain.d.ts +28 -0
- package/dist/src/middleware/subdomain.d.ts.map +1 -0
- package/dist/src/middleware/subdomain.js +32 -0
- package/dist/src/middleware/subdomain.js.map +1 -0
- package/dist/src/models/offer-schemas.d.ts +229 -0
- package/dist/src/models/offer-schemas.d.ts.map +1 -0
- package/dist/src/models/offer-schemas.js +100 -0
- package/dist/src/models/offer-schemas.js.map +1 -0
- package/dist/src/routes/agent-tasks.d.ts.map +1 -1
- package/dist/src/routes/agent-tasks.js +33 -0
- package/dist/src/routes/agent-tasks.js.map +1 -1
- package/dist/src/routes/agents.d.ts.map +1 -1
- package/dist/src/routes/agents.js +146 -1
- package/dist/src/routes/agents.js.map +1 -1
- package/dist/src/routes/apps-backup.d.ts +20 -0
- package/dist/src/routes/apps-backup.d.ts.map +1 -0
- package/dist/src/routes/apps-backup.js +165 -0
- package/dist/src/routes/apps-backup.js.map +1 -0
- package/dist/src/routes/ghii.d.ts.map +1 -1
- package/dist/src/routes/ghii.js +4 -0
- package/dist/src/routes/ghii.js.map +1 -1
- package/dist/src/routes/instances.d.ts +2 -1
- package/dist/src/routes/instances.d.ts.map +1 -1
- package/dist/src/routes/instances.js +59 -1
- package/dist/src/routes/instances.js.map +1 -1
- package/dist/src/routes/packages.d.ts.map +1 -1
- package/dist/src/routes/packages.js +32 -0
- package/dist/src/routes/packages.js.map +1 -1
- package/dist/src/routes/permissions.d.ts +12 -0
- package/dist/src/routes/permissions.d.ts.map +1 -1
- package/dist/src/routes/permissions.js +35 -3
- package/dist/src/routes/permissions.js.map +1 -1
- package/dist/src/routes/portal.d.ts +3 -0
- package/dist/src/routes/portal.d.ts.map +1 -1
- package/dist/src/routes/portal.js +12 -0
- package/dist/src/routes/portal.js.map +1 -1
- package/dist/src/routes/public-stats.d.ts +15 -0
- package/dist/src/routes/public-stats.d.ts.map +1 -0
- package/dist/src/routes/public-stats.js +90 -0
- package/dist/src/routes/public-stats.js.map +1 -0
- package/dist/src/routes/sse.d.ts +7 -2
- package/dist/src/routes/sse.d.ts.map +1 -1
- package/dist/src/routes/sse.js +35 -5
- package/dist/src/routes/sse.js.map +1 -1
- package/dist/src/routes/subdomains.d.ts +33 -0
- package/dist/src/routes/subdomains.d.ts.map +1 -0
- package/dist/src/routes/subdomains.js +208 -0
- package/dist/src/routes/subdomains.js.map +1 -0
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +12 -1
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +4 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/services/apps-backup-export.d.ts +22 -0
- package/dist/src/services/apps-backup-export.d.ts.map +1 -0
- package/dist/src/services/apps-backup-export.js +136 -0
- package/dist/src/services/apps-backup-export.js.map +1 -0
- package/dist/src/services/apps-backup-import.d.ts +120 -0
- package/dist/src/services/apps-backup-import.d.ts.map +1 -0
- package/dist/src/services/apps-backup-import.js +259 -0
- package/dist/src/services/apps-backup-import.js.map +1 -0
- package/dist/src/services/component-registrar.d.ts +17 -0
- package/dist/src/services/component-registrar.d.ts.map +1 -1
- package/dist/src/services/component-registrar.js +63 -5
- package/dist/src/services/component-registrar.js.map +1 -1
- package/dist/src/services/handbooks/appdev.d.ts +1 -1
- package/dist/src/services/handbooks/appdev.d.ts.map +1 -1
- package/dist/src/services/handbooks/appdev.js +5 -2
- package/dist/src/services/handbooks/appdev.js.map +1 -1
- package/dist/src/storage/interface.d.ts +11 -1
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/storage/interface.js.map +1 -1
- package/dist/src/storage/providers/mongodb/index.d.ts +10 -1
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +76 -0
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/index.d.ts +8 -1
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +61 -0
- package/dist/src/storage/providers/sqlite/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +11 -0
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/index.d.ts +1 -0
- package/dist/src/storage/repositories/index.d.ts.map +1 -1
- package/dist/src/storage/repositories/package.repository.d.ts +2 -0
- package/dist/src/storage/repositories/package.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/subdomain-site.repository.d.ts +19 -0
- package/dist/src/storage/repositories/subdomain-site.repository.d.ts.map +1 -0
- package/dist/src/storage/repositories/subdomain-site.repository.js +2 -0
- package/dist/src/storage/repositories/subdomain-site.repository.js.map +1 -0
- package/dist/static/app-catalog.html +1139 -28
- package/package.json +1 -1
- package/prisma/schema.postgres.prisma +11 -0
- package/prisma/schema.prisma +11 -0
|
@@ -138,6 +138,25 @@
|
|
|
138
138
|
* v1.25.2 — 2026-06-10 — Settings Cancel works again: it discards changes AND closes the panel
|
|
139
139
|
* (both organism + workspace settings) — the dirty-only reset variant did nothing visible on a
|
|
140
140
|
* clean form, which read as a dead button.
|
|
141
|
+
* v1.26.0 — 2026-06-10 — Contract-agent discovery: agents tagged 'workspace-contract' (+ optional
|
|
142
|
+
* 'contract.<id>' names) surface to their OWNER — a 📜 block in the workspace People panel and a
|
|
143
|
+
* 📜 badge/marker in the organism Agents tab picker + rows. Tag convention: docs/agent-workspace-
|
|
144
|
+
* contracts.md §7b; helpers offersWorkspaceContract/contractNamesOf in /js/services/agents.js.
|
|
145
|
+
* v1.27.0 — 2026-06-10 — One-click contract adoption: each contract chip gets an "Adopt <id>"
|
|
146
|
+
* button that queues the agreed adopt-contract TASK for the agent (scope[kind]=adopt-contract +
|
|
147
|
+
* organism_id/ws/contract, status:'queued' — the create default is draft and a draft never
|
|
148
|
+
* auto-activates; §7c) — the agent joins/provisions its own spaces and the task completion is
|
|
149
|
+
* the ack. An agent already working in THIS workspace (per the participants data) shows a
|
|
150
|
+
* "✓ active here" badge instead of Adopt. Convention agreed with the crewaimeat side 2026-06-10.
|
|
151
|
+
* v1.28.0 — 2026-06-12 — Overview landing tab: the whole workspace on one vertical scroll — a
|
|
152
|
+
* "what happened here" strip (last 8 activity events, humanized + clickable) above every
|
|
153
|
+
* manifest space stacked in order (records: compact rows max 5 + "Show all N →"; documents:
|
|
154
|
+
* title + first-line preview; empty space: one row with an Add button; non-memory backing:
|
|
155
|
+
* one notice row). Overview is ALWAYS the landing view (tab persistence dropped — it hid
|
|
156
|
+
* content). Mobile: sections collapse as accordions and documents expand INLINE (view+edit,
|
|
157
|
+
* no popout window). Per-tab unseen badges (gray, never red) from localStorage seen marks vs
|
|
158
|
+
* activity events — cleared when the tab opens, surviving across sessions; first visit seeds
|
|
159
|
+
* a baseline so history is never "unseen"; own non-agent edits don't count.
|
|
141
160
|
*/
|
|
142
161
|
import { h } from 'preact';
|
|
143
162
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'preact/hooks';
|
|
@@ -152,12 +171,13 @@ import { SearchBar } from '/components/SearchBar.js';
|
|
|
152
171
|
import * as orgService from '/js/services/organisms.js';
|
|
153
172
|
import * as memoryService from '/js/services/memory.js';
|
|
154
173
|
import * as knowledgeService from '/js/services/knowledge.js';
|
|
155
|
-
import { listAgents } from '/js/services/agents.js';
|
|
174
|
+
import { listAgents, offersWorkspaceContract, contractNamesOf, adoptContractTask } from '/js/services/agents.js';
|
|
156
175
|
import { getGhii } from '/js/services/auth.js';
|
|
157
176
|
import { listPosts, createPost } from '/js/services/boards.js';
|
|
158
177
|
import { OpenRouterSettings } from './generator-settings.js';
|
|
159
178
|
import { copyToClipboard } from '/js/utils.js';
|
|
160
179
|
import { dt, fmtBytes } from '/js/format.js';
|
|
180
|
+
import { recordRecent } from '/js/recents.js';
|
|
161
181
|
import { Markdown, slugifyHeading } from '/components/Markdown.js';
|
|
162
182
|
import { Mermaid } from '/components/Mermaid.js';
|
|
163
183
|
|
|
@@ -422,7 +442,7 @@ function OrgAgentsPanel({ org, ghii, canManage, showToast, onChanged }) {
|
|
|
422
442
|
<div class="flex-row-wrap">
|
|
423
443
|
<select class="input-field input-sm" value=${pick} onChange=${e => setPick(e.target.value)}>
|
|
424
444
|
<option value="">${t('organisms.pickAgent') || 'Choose one of your agents…'}</option>
|
|
425
|
-
${pickable.map(a => html`<option value=${a.gaii} key=${a.gaii}>${a.display_name || a.name}</option>`)}
|
|
445
|
+
${pickable.map(a => html`<option value=${a.gaii} key=${a.gaii}>${(a.display_name || a.name) + (offersWorkspaceContract(a) ? ` · 📜 ${t('organisms.contractTag') || 'contract'}` : '')}</option>`)}
|
|
426
446
|
</select>
|
|
427
447
|
<button class="btn-outline btn-sm" disabled=${busy || !pick} onClick=${() => attach(pick)}>${t('organisms.attach') || 'Attach'}</button>
|
|
428
448
|
</div>` : html`
|
|
@@ -448,6 +468,7 @@ function OrgAgentsPanel({ org, ghii, canManage, showToast, onChanged }) {
|
|
|
448
468
|
<div class="pj-org-titlerow">
|
|
449
469
|
<span class="pj-org-name">${(own?.display_name || p.name)}</span>
|
|
450
470
|
${p.node ? html`<span class="badge badge-info">${(p.node)}</span>` : null}
|
|
471
|
+
${own && offersWorkspaceContract(own) ? html`<span class="badge badge-success" title=${(t('organisms.contractAgentHint') || 'Advertises a workspace contract') + (contractNamesOf(own).length ? `: ${contractNamesOf(own).join(', ')}` : '')}>${'📜 '}${t('organisms.contractTag') || 'contract'}</span>` : null}
|
|
451
472
|
</div>
|
|
452
473
|
<div class="pj-org-desc" title=${g}>
|
|
453
474
|
<span class="mono">${(p.owner ? `${p.name}#${p.owner}` : g)}</span>
|
|
@@ -1167,6 +1188,11 @@ function OrganismHome({ org, ghii, showToast, initialSettings, onOpenWs, onBack,
|
|
|
1167
1188
|
return () => { cancelled = true; window.removeEventListener('aimeat-live-update', fetchIt); };
|
|
1168
1189
|
}, [org.id, canEdit]);
|
|
1169
1190
|
|
|
1191
|
+
// Feed the home page's "Continue" list (only once the real name is known, not the {id} stub).
|
|
1192
|
+
useEffect(() => {
|
|
1193
|
+
if (org.name) recordRecent({ type: 'organism', id: org.id, label: org.name, data: { orgId: org.id } });
|
|
1194
|
+
}, [org.id, org.name]);
|
|
1195
|
+
|
|
1170
1196
|
/* ── Settings: labelled + grouped form (Identity / Access) with live dirty-check, a one-line
|
|
1171
1197
|
* read-only metadata row, per-choice access hints, and a clearly separated danger zone whose
|
|
1172
1198
|
* delete needs the organism's name typed and states exactly what gets removed. ── */
|
|
@@ -1788,13 +1814,33 @@ function ActivityPanel({ orgId, wsId }) {
|
|
|
1788
1814
|
/* Participants panel — who takes part in this workspace, as a node → owner → agents chart plus a
|
|
1789
1815
|
* listing. Built from the records' identity traces (humans + their agents) + organism membership.
|
|
1790
1816
|
* The viewer's own agents are named; everyone else's appear as anonymous ghost boxes. */
|
|
1791
|
-
function ParticipantsPanel({ orgId, wsId }) {
|
|
1817
|
+
function ParticipantsPanel({ orgId, wsId, showToast }) {
|
|
1792
1818
|
const [data, setData] = useState(null);
|
|
1793
1819
|
const [show, setShow] = useState(true);
|
|
1794
1820
|
const [access, setAccess] = useState(null); // { requests, members } — only for the workspace creator
|
|
1795
1821
|
const [grantee, setGrantee] = useState('');
|
|
1796
1822
|
const [role, setRole] = useState('contributor');
|
|
1797
1823
|
const [busy, setBusy] = useState(false);
|
|
1824
|
+
// The viewer's own agents advertising a workspace contract (tag 'workspace-contract' +
|
|
1825
|
+
// optional 'contract.<name>' tags) — surfaced here so a user looking at a workspace sees
|
|
1826
|
+
// straight away which of their agents can serve it.
|
|
1827
|
+
const [contractAgents, setContractAgents] = useState([]);
|
|
1828
|
+
const [adoptBusy, setAdoptBusy] = useState(''); // `${gaii}:${contract}` of the in-flight adopt
|
|
1829
|
+
useEffect(() => {
|
|
1830
|
+
listAgents().then(a => setContractAgents((a || []).filter(offersWorkspaceContract))).catch(() => {});
|
|
1831
|
+
}, []);
|
|
1832
|
+
// One-click adoption: queue the agreed adopt-contract task (docs/agent-workspace-contracts.md
|
|
1833
|
+
// §7c) — the agent provisions its contract's spaces itself and the task completion is the ack.
|
|
1834
|
+
const adopt = async (a, contract) => {
|
|
1835
|
+
const key = `${a.gaii}:${contract || ''}`;
|
|
1836
|
+
setAdoptBusy(key);
|
|
1837
|
+
try {
|
|
1838
|
+
const r = await adoptContractTask(a.name, { organismId: orgId, ws: wsId, contract });
|
|
1839
|
+
if (r?.ok === false) showToast?.(r?.error?.message || (t('organisms.adoptFailed') || 'Could not queue the adoption task'));
|
|
1840
|
+
else showToast?.((t('organisms.adoptQueued') || 'Adoption task queued for {agent} — it provisions the contract and reports back').replace('{agent}', a.display_name || a.name));
|
|
1841
|
+
} catch (e) { showToast?.((e && e.message) || (t('organisms.adoptFailed') || 'Could not queue the adoption task')); }
|
|
1842
|
+
finally { setAdoptBusy(''); }
|
|
1843
|
+
};
|
|
1798
1844
|
useEffect(() => {
|
|
1799
1845
|
let cancelled = false;
|
|
1800
1846
|
const fetchIt = () => orgService.getWorkspaceParticipants(orgId, wsId).then(d => { if (!cancelled) setData(d); }).catch(() => {});
|
|
@@ -1824,6 +1870,35 @@ function ParticipantsPanel({ orgId, wsId }) {
|
|
|
1824
1870
|
</div>
|
|
1825
1871
|
${show ? html`
|
|
1826
1872
|
<div class="pj-parts">
|
|
1873
|
+
${contractAgents.length ? html`
|
|
1874
|
+
<div class="pj-contract-agents">
|
|
1875
|
+
<div class="pj-access-title">${'📜 '}${t('organisms.contractAgents') || 'Your contract agents'}</div>
|
|
1876
|
+
<div class="section-desc">${t('organisms.contractAgentsDesc') || 'These agents of yours advertise a workspace contract — they can process a workspace like this one. Grant access (below) or attach them in the organism Agents tab.'}</div>
|
|
1877
|
+
<div class="pj-part-agents">
|
|
1878
|
+
${contractAgents.map(a => {
|
|
1879
|
+
const names = contractNamesOf(a);
|
|
1880
|
+
// An agent already working in THIS workspace (it appears among the viewer's own
|
|
1881
|
+
// agents in the participants data) gets a ✓ instead of Adopt buttons — offering
|
|
1882
|
+
// adoption for an agent that is already here is noise, and the task would be a
|
|
1883
|
+
// no-op re-provision anyway.
|
|
1884
|
+
const here = owners.some(o => o.isSelf && (o.agents || []).some(ag => ag.isOwn && ag.name === a.name));
|
|
1885
|
+
// One adopt action per advertised contract (a single unnamed one falls back to
|
|
1886
|
+
// the bare marker). The agent does the rest — join, provision, complete the task.
|
|
1887
|
+
const actions = names.length ? names : [''];
|
|
1888
|
+
return html`
|
|
1889
|
+
<span class="pj-part-agent own" key=${a.gaii} title=${a.gaii}>
|
|
1890
|
+
${'📜 '}${a.display_name || a.name}
|
|
1891
|
+
${here ? html`<span class="badge badge-success pj-mini" title=${t('organisms.contractActiveHint') || 'This agent already works in this workspace'}>${'✓ '}${t('organisms.contractActive') || 'active here'}</span>`
|
|
1892
|
+
: actions.map(c => html`
|
|
1893
|
+
<button class="btn-outline btn-sm pj-adopt-btn" key=${c} disabled=${adoptBusy === `${a.gaii}:${c}`}
|
|
1894
|
+
title=${t('organisms.adoptHint') || 'Queue a task for this agent to adopt its contract into THIS workspace (it provisions the spaces itself)'}
|
|
1895
|
+
onClick=${() => adopt(a, c)}>
|
|
1896
|
+
${adoptBusy === `${a.gaii}:${c}` ? '…' : `${t('organisms.adoptContract') || 'Adopt'}${c ? ` ${c}` : ''}`}
|
|
1897
|
+
</button>`)}
|
|
1898
|
+
</span>`;
|
|
1899
|
+
})}
|
|
1900
|
+
</div>
|
|
1901
|
+
</div>` : null}
|
|
1827
1902
|
<${Mermaid} chart=${orgService.buildParticipantsMermaid(data)} />
|
|
1828
1903
|
<div class="pj-parts-list">
|
|
1829
1904
|
${owners.map((o, i) => html`<div class="pj-part-owner" key=${i}>
|
|
@@ -1967,9 +2042,15 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
1967
2042
|
const [wsName, setWsName] = useState('');
|
|
1968
2043
|
useEffect(() => {
|
|
1969
2044
|
let cancelled = false;
|
|
1970
|
-
orgService.listWorkspaces(orgId).then(list => {
|
|
2045
|
+
orgService.listWorkspaces(orgId).then(list => {
|
|
2046
|
+
if (cancelled) return;
|
|
2047
|
+
const name = (list.find(w => w.id === wsId)?.name) || '';
|
|
2048
|
+
setWsName(name);
|
|
2049
|
+
// Feed the home page's "Continue" list with a real display name.
|
|
2050
|
+
recordRecent({ type: 'workspace', id: `${orgId}/${wsId}`, label: name || wsId, sub: org.name || '', data: { orgId, wsId } });
|
|
2051
|
+
}).catch(() => {});
|
|
1971
2052
|
return () => { cancelled = true; };
|
|
1972
|
-
}, [orgId, wsId]);
|
|
2053
|
+
}, [orgId, wsId]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
1973
2054
|
// Pop a document out into its own window (served by doc-solo.js) so several can sit side by side,
|
|
1974
2055
|
// each independent. The window name is unique per document, so re-clicking focuses the open one.
|
|
1975
2056
|
const popOut = (typeName, docId) => {
|
|
@@ -2005,11 +2086,10 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
2005
2086
|
} catch (e) { /* noop */ }
|
|
2006
2087
|
return null;
|
|
2007
2088
|
});
|
|
2008
|
-
// Active tab —
|
|
2009
|
-
//
|
|
2010
|
-
|
|
2011
|
-
const [tab,
|
|
2012
|
-
const setTab = (id) => { setTabState(id); try { sessionStorage.setItem(tabStoreKey, id); } catch (e) { /* noop */ } };
|
|
2089
|
+
// Active tab — Overview first (ALWAYS the landing view: the whole workspace on one scroll),
|
|
2090
|
+
// then one tab per manifest space + fixed Sources/Share/Review/Activity/People. Deliberately
|
|
2091
|
+
// NOT persisted: opening a workspace must show the overview, not whatever tab was last open.
|
|
2092
|
+
const [tab, setTab] = useState('overview');
|
|
2013
2093
|
const [sectionsByType, setSectionsByType] = useState({}); // { typeName: [{id,name,parentId,documents:[docId]}] }
|
|
2014
2094
|
const [editingSec, setEditingSec] = useState(null); // section id currently being renamed inline
|
|
2015
2095
|
const draggedDoc = useRef(null); // { type, id } of the doc being dragged
|
|
@@ -2023,16 +2103,50 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
2023
2103
|
const [showFlow, setShowFlow] = useState(true); // the manifest-defined edit-flow chart (first item)
|
|
2024
2104
|
const [share, setShare] = useState(null); // { public, spaces, docs } — lazy-loaded when Settings opens
|
|
2025
2105
|
const [shareBusy, setShareBusy] = useState(false);
|
|
2106
|
+
const [wsEvents, setWsEvents] = useState([]); // activity events — Overview strip + per-tab unseen badges
|
|
2107
|
+
const [ovOpen, setOvOpen] = useState({}); // Overview accordion: { spaceName: bool } (mobile starts collapsed)
|
|
2108
|
+
const [ovDoc, setOvDoc] = useState(null); // Overview inline document (mobile): { type, id, mode }
|
|
2109
|
+
|
|
2110
|
+
// ── Unseen-change tracking: per-tab "seen" marks in localStorage (they survive sessions).
|
|
2111
|
+
// A tab's badge counts items changed since the tab was last opened; opening it (or changes
|
|
2112
|
+
// landing while it is open) marks it seen. First-ever visit seeds a baseline at "now", so
|
|
2113
|
+
// history never shows as unseen. Neutral gray badges only — red is reserved for errors.
|
|
2114
|
+
const seenKey = 'aimeat.ws.' + orgId + '.' + wsId + '.seen';
|
|
2115
|
+
const [seen, setSeen] = useState(() => {
|
|
2116
|
+
try { const raw = localStorage.getItem(seenKey); return raw ? JSON.parse(raw) : null; } catch (e) { return null; }
|
|
2117
|
+
});
|
|
2118
|
+
const markSeen = useCallback((tabId) => setSeen(prev => {
|
|
2119
|
+
const next = { ...(prev || {}), [tabId]: new Date().toISOString() };
|
|
2120
|
+
try { localStorage.setItem(seenKey, JSON.stringify(next)); } catch (e) { /* noop */ }
|
|
2121
|
+
return next;
|
|
2122
|
+
}), [seenKey]);
|
|
2123
|
+
// Items (not raw events) changed since the tab's seen mark. The user's own non-agent edits
|
|
2124
|
+
// are not news to them, so they never count. Event actors come as the BARE owner name from
|
|
2125
|
+
// the version history, so compare against both the bare name and the full GHII.
|
|
2126
|
+
const unseenOf = useCallback((tabId) => {
|
|
2127
|
+
if (!seen) return 0;
|
|
2128
|
+
const base = seen[tabId] || seen.__base;
|
|
2129
|
+
if (!base) return 0;
|
|
2130
|
+
const me = getGhii();
|
|
2131
|
+
const meBare = String(me || '').split('@')[0];
|
|
2132
|
+
const isOwn = (e) => !e.agent && me && (e.actor === me || e.actor === meBare);
|
|
2133
|
+
const match = tabId.startsWith('space:') ? (e) => e.type === tabId.slice(6)
|
|
2134
|
+
: (tabId === 'activity' ? () => true : () => false);
|
|
2135
|
+
const evs = wsEvents.filter(e => (e.at || '') > base && match(e) && !isOwn(e));
|
|
2136
|
+
return new Set(evs.map(e => (e.type || '') + ':' + (e.instance || ''))).size;
|
|
2137
|
+
}, [seen, wsEvents]);
|
|
2026
2138
|
|
|
2027
2139
|
const load = useCallback(async () => {
|
|
2028
2140
|
const w = await orgService.getWorkspace(orgId, wsId).catch(() => null);
|
|
2029
2141
|
if (w && w.manifest) {
|
|
2030
|
-
const [ap, cfg, secs] = await Promise.all([
|
|
2142
|
+
const [ap, cfg, secs, act] = await Promise.all([
|
|
2031
2143
|
orgService.listApprovals(orgId, 'pending').catch(() => []),
|
|
2032
2144
|
orgService.getConfig(orgId).catch(() => ({})),
|
|
2033
2145
|
orgService.getAllSections(orgId, wsId).catch(() => ({})),
|
|
2146
|
+
orgService.getWorkspaceActivity(orgId, wsId).catch(() => ({ events: [] })),
|
|
2034
2147
|
]);
|
|
2035
2148
|
setApprovals(ap); setGateOn(!!(cfg?.gates?.publish?.enabled)); setSectionsByType(secs);
|
|
2149
|
+
setWsEvents(act?.events || []);
|
|
2036
2150
|
}
|
|
2037
2151
|
setWs(w && w.manifest ? w : null);
|
|
2038
2152
|
}, [orgId, wsId]);
|
|
@@ -2054,6 +2168,22 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
2054
2168
|
return () => window.removeEventListener('aimeat-live-update', handler);
|
|
2055
2169
|
}, []);
|
|
2056
2170
|
|
|
2171
|
+
// First-ever visit: seed the seen baseline once the workspace has loaded — no badges for history.
|
|
2172
|
+
useEffect(() => {
|
|
2173
|
+
if (seen !== null || ws === undefined) return;
|
|
2174
|
+
const next = { __base: new Date().toISOString() };
|
|
2175
|
+
try { localStorage.setItem(seenKey, JSON.stringify(next)); } catch (e) { /* noop */ }
|
|
2176
|
+
setSeen(next);
|
|
2177
|
+
}, [seen, ws]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
2178
|
+
|
|
2179
|
+
// Changes landing on the tab the user is LOOKING at are seen immediately — otherwise a draft
|
|
2180
|
+
// saved while you're on its space tab would raise that tab's own badge.
|
|
2181
|
+
useEffect(() => {
|
|
2182
|
+
if (!seen || showSettings) return;
|
|
2183
|
+
const cur = tab || 'overview';
|
|
2184
|
+
if (unseenOf(cur) > 0) markSeen(cur);
|
|
2185
|
+
}, [wsEvents, tab, showSettings, seen, unseenOf, markSeen]);
|
|
2186
|
+
|
|
2057
2187
|
const setup = useCallback(async () => {
|
|
2058
2188
|
setBusy(true);
|
|
2059
2189
|
try {
|
|
@@ -2466,6 +2596,23 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
2466
2596
|
const isDocSpace = orgService.isDocSpace;
|
|
2467
2597
|
const draftsFor = (name) => (ws.drafts && ws.drafts[name]) || [];
|
|
2468
2598
|
const objectsFor = (name) => (ws.objects && ws.objects[name]) || [];
|
|
2599
|
+
// One entry per id, draft (working copy) taking precedence over its published version —
|
|
2600
|
+
// the index must show the draft badge even when a published `.latest` also exists.
|
|
2601
|
+
const mergedDocs = (ot) => {
|
|
2602
|
+
const byId = new Map();
|
|
2603
|
+
for (const d of objectsFor(ot.name)) byId.set(d.id, { ...d, _draft: false, _published: true });
|
|
2604
|
+
for (const d of draftsFor(ot.name)) {
|
|
2605
|
+
const pub = byId.get(d.id); // kept on `_pub` for the view's Draft/Published toggle
|
|
2606
|
+
byId.set(d.id, { ...d, _draft: true, _published: !!pub, _pub: pub || null });
|
|
2607
|
+
}
|
|
2608
|
+
return [...byId.values()];
|
|
2609
|
+
};
|
|
2610
|
+
const mergedRecords = (ot) => {
|
|
2611
|
+
const byId = new Map();
|
|
2612
|
+
for (const o of objectsFor(ot.name)) byId.set(o.id, { ...o, _draft: false });
|
|
2613
|
+
for (const d of draftsFor(ot.name)) byId.set(d.id, { ...d, _draft: true });
|
|
2614
|
+
return [...byId.values()];
|
|
2615
|
+
};
|
|
2469
2616
|
|
|
2470
2617
|
// ── Public sharing (meta.share) — what published document-space pages anyone can read via the
|
|
2471
2618
|
// no-login viewer. Independent of the access roles. Lazy-loaded the first time the panel opens. ──
|
|
@@ -2500,17 +2647,7 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
2500
2647
|
// tied to a section's documents[] (or unsorted). Edits to the tree persist immediately.
|
|
2501
2648
|
const renderDocSpace = (ot) => {
|
|
2502
2649
|
const secs = sectionsByType[ot.name] || [];
|
|
2503
|
-
|
|
2504
|
-
// version, so the index shows the draft badge even when a published `.latest` also exists.
|
|
2505
|
-
// (Without this, keying docById by id let the published entry overwrite the draft and the
|
|
2506
|
-
// "draft" badge silently disappeared whenever a doc had both versions.)
|
|
2507
|
-
const byId = new Map();
|
|
2508
|
-
for (const d of objectsFor(ot.name)) byId.set(d.id, { ...d, _draft: false, _published: true });
|
|
2509
|
-
for (const d of draftsFor(ot.name)) {
|
|
2510
|
-
const pub = byId.get(d.id); // the published version, if one exists — kept on `_pub` for the view's Draft/Published toggle
|
|
2511
|
-
byId.set(d.id, { ...d, _draft: true, _published: !!pub, _pub: pub || null });
|
|
2512
|
-
}
|
|
2513
|
-
const docs = [...byId.values()];
|
|
2650
|
+
const docs = mergedDocs(ot);
|
|
2514
2651
|
const docById = {}; docs.forEach(d => { docById[d.id] = d; });
|
|
2515
2652
|
const used = new Set(); secs.forEach(s => (s.documents || []).forEach(id => used.add(id)));
|
|
2516
2653
|
const unsorted = docs.filter(d => !used.has(d.id));
|
|
@@ -2665,12 +2802,13 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
2665
2802
|
{ id: 'activity', label: t('organisms.activity') || 'Activity', count: null },
|
|
2666
2803
|
{ id: 'people', label: t('organisms.tabPeople') || 'People', count: null },
|
|
2667
2804
|
];
|
|
2668
|
-
const wsTabs = [...spaceTabs, ...fixedTabs];
|
|
2805
|
+
const wsTabs = [{ id: 'overview', label: t('organisms.tabOverview') || 'Overview', count: null }, ...spaceTabs, ...fixedTabs];
|
|
2669
2806
|
// Settings REPLACES the tab content: while it is open no tab is active (the previous state — a
|
|
2670
2807
|
// highlighted tab above settings content — lied about what the user was looking at).
|
|
2671
|
-
const activeTab = showSettings ? '' : (wsTabs.some(tb => tb.id === tab) ? tab :
|
|
2808
|
+
const activeTab = showSettings ? '' : (wsTabs.some(tb => tb.id === tab) ? tab : 'overview');
|
|
2672
2809
|
const activeSpace = activeTab.startsWith('space:') ? allTypes.find(ot => ot.name === activeTab.slice(6)) : null;
|
|
2673
|
-
|
|
2810
|
+
// Opening a tab clears its unseen badge (the seen mark persists across sessions).
|
|
2811
|
+
const pickTab = (id) => guardWsDirty(() => { setShowSettings(false); setTab(id); markSeen(id); });
|
|
2674
2812
|
|
|
2675
2813
|
// Menu items are VERBS (a click copies to the clipboard; the toast confirms). The contract-agent
|
|
2676
2814
|
// builder is a different category from the two copy actions, so a divider separates it.
|
|
@@ -2681,6 +2819,123 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
2681
2819
|
{ label: t('organisms.contractPrompt') || 'Create contract agent', icon: '⚙️', onClick: copyContractPrompt },
|
|
2682
2820
|
];
|
|
2683
2821
|
|
|
2822
|
+
// ── Overview (landing tab): the whole workspace on one vertical scroll — a "what happened
|
|
2823
|
+
// here" strip from the activity feed, then every manifest space as its own stacked section.
|
|
2824
|
+
// Tabs stay for focused work; this answers "where is everything and what changed".
|
|
2825
|
+
const isMobileView = () => window.matchMedia('(max-width: 640px)').matches;
|
|
2826
|
+
const shortActor = (a) => String(a || '').split('@')[0];
|
|
2827
|
+
// First non-empty markdown line as a gray preview (heading/list markers stripped).
|
|
2828
|
+
const firstLine = (md) => {
|
|
2829
|
+
const line = String(md || '').split('\n').map(s => s.trim()).find(s => s && !s.startsWith('```'));
|
|
2830
|
+
return line ? line.replace(/^#{1,6}\s+/, '').replace(/^[-*>]\s+/, '').slice(0, 120) : '';
|
|
2831
|
+
};
|
|
2832
|
+
// Strip event → jump straight to the changed item in its space tab.
|
|
2833
|
+
const gotoEvent = (e) => {
|
|
2834
|
+
const target = allTypes.find(o => o.name === e.type);
|
|
2835
|
+
if (!target || !orgService.isMemorySpace(target)) { pickTab('activity'); return; }
|
|
2836
|
+
if (isDocSpace(target)) setActiveDoc({ type: target.name, mode: 'view', page: { id: e.instance } });
|
|
2837
|
+
else setExpandedRec(s => ({ ...s, [target.name + ':' + e.instance]: true }));
|
|
2838
|
+
pickTab('space:' + target.name);
|
|
2839
|
+
};
|
|
2840
|
+
const openOvRec = (ot, r) => { setExpandedRec(s => ({ ...s, [ot.name + ':' + r.id]: true })); pickTab('space:' + ot.name); };
|
|
2841
|
+
// A document opens in its space tab on desktop; on mobile it expands INLINE right here —
|
|
2842
|
+
// view and edit both — so no window juggling is ever needed on a phone.
|
|
2843
|
+
const openOvDoc = (ot, d) => {
|
|
2844
|
+
if (isMobileView()) setOvDoc(v => (v && v.type === ot.name && v.id === d.id) ? null : { type: ot.name, id: d.id, mode: 'view' });
|
|
2845
|
+
else { setActiveDoc({ type: ot.name, mode: 'view', page: { id: d.id } }); pickTab('space:' + ot.name); }
|
|
2846
|
+
};
|
|
2847
|
+
const ovAddNew = (ot, docMode) => {
|
|
2848
|
+
if (docMode) setActiveDoc({ type: ot.name, mode: 'edit', page: { id: '', title: '', markdown: '' } });
|
|
2849
|
+
else startAdd(ot);
|
|
2850
|
+
pickTab('space:' + ot.name);
|
|
2851
|
+
};
|
|
2852
|
+
|
|
2853
|
+
const renderOvDocInline = (ot, d) => ovDoc.mode === 'edit'
|
|
2854
|
+
? html`<div class="pj-ov-doc-inline"><${DocumentEditor} key=${'oved-' + d.id} orgId=${orgId} page=${d} busy=${busy}
|
|
2855
|
+
onSave=${(p) => { savePage(ot, p); setOvDoc({ type: ot.name, id: d.id, mode: 'view' }); }}
|
|
2856
|
+
onCancel=${() => setOvDoc({ type: ot.name, id: d.id, mode: 'view' })} /></div>`
|
|
2857
|
+
: html`<div class="pj-ov-doc-inline"><${DocumentView} key=${'ovv-' + d.id} page=${d} busy=${busy}
|
|
2858
|
+
onEdit=${() => setOvDoc({ type: ot.name, id: d.id, mode: 'edit' })}
|
|
2859
|
+
onPublish=${() => publish(ot, d.id)}
|
|
2860
|
+
onWikiLink=${(content) => {
|
|
2861
|
+
const title = String(content).split('#')[0].trim();
|
|
2862
|
+
const targetDoc = title && mergedDocs(ot).find(x => (x.title || '').toLowerCase() === title.toLowerCase());
|
|
2863
|
+
if (targetDoc) setOvDoc({ type: ot.name, id: targetDoc.id, mode: 'view' });
|
|
2864
|
+
}} /></div>`;
|
|
2865
|
+
|
|
2866
|
+
const renderOvSection = (ot) => {
|
|
2867
|
+
const memory = orgService.isMemorySpace(ot);
|
|
2868
|
+
const docMode = memory && isDocSpace(ot);
|
|
2869
|
+
const items = memory ? (docMode ? mergedDocs(ot) : mergedRecords(ot)) : [];
|
|
2870
|
+
const label = cap(wsT('type.' + ot.name) || ot.name);
|
|
2871
|
+
// A space the manifest declares but memory doesn't back → one notice row, never hidden.
|
|
2872
|
+
if (!memory) return html`
|
|
2873
|
+
<div class="pj-ov-row" key=${ot.name}>
|
|
2874
|
+
<button class="pj-ov-name pj-ov-name-link" onClick=${() => pickTab('space:' + ot.name)}>${label}</button>
|
|
2875
|
+
<span class="badge badge-warn">${String(ot.backing)}</span>
|
|
2876
|
+
</div>`;
|
|
2877
|
+
// Empty space → a single compact row (name + none + add), not an empty box.
|
|
2878
|
+
if (items.length === 0) return html`
|
|
2879
|
+
<div class="pj-ov-row" key=${ot.name}>
|
|
2880
|
+
<span class="pj-ov-name">${label}</span>
|
|
2881
|
+
<span class="pj-muted">${t('organisms.noneYet') || 'none yet'}</span>
|
|
2882
|
+
<button class="btn-ghost btn-sm" onClick=${() => ovAddNew(ot, docMode)}>${'+ '}${docMode ? (t('organisms.newPage') || 'New document') : (t('organisms.addDraft') || 'Add draft')}</button>
|
|
2883
|
+
</div>`;
|
|
2884
|
+
const open = ovOpen[ot.name] ?? !isMobileView();
|
|
2885
|
+
const shown = items.slice(0, 5);
|
|
2886
|
+
return html`
|
|
2887
|
+
<div class="pj-ov-sec" key=${ot.name}>
|
|
2888
|
+
<div class="pj-ov-sec-head" onClick=${() => setOvOpen(s => ({ ...s, [ot.name]: !open }))}>
|
|
2889
|
+
<span class="pj-ov-chevron">${open ? '▾' : '▸'}</span>
|
|
2890
|
+
<span class="pj-ov-name">${label}</span>
|
|
2891
|
+
<span class="pj-org-tab-count">${items.length}</span>
|
|
2892
|
+
${docMode ? html`<span class="pj-doc-tag">${t('organisms.docs') || 'docs'}</span>` : null}
|
|
2893
|
+
<span class="pj-ov-spacer"></span>
|
|
2894
|
+
<button class="btn-ghost btn-sm" onClick=${(ev) => { ev.stopPropagation(); ovAddNew(ot, docMode); }}>${'+ '}${docMode ? (t('organisms.newPage') || 'New document') : (t('organisms.addDraft') || 'Add draft')}</button>
|
|
2895
|
+
</div>
|
|
2896
|
+
${open ? html`
|
|
2897
|
+
<div class="pj-ov-items">
|
|
2898
|
+
${shown.map(d => docMode ? html`
|
|
2899
|
+
<div class="pj-ov-doc" key=${d.id}>
|
|
2900
|
+
<button class="pj-ov-item" onClick=${() => openOvDoc(ot, d)}>
|
|
2901
|
+
${d._draft ? html`<span class="badge badge-warn pj-mini">${t('organisms.draft') || 'draft'}</span>` : null}
|
|
2902
|
+
<span class="pj-ov-item-title">${d.title || d.id}</span>
|
|
2903
|
+
<span class="pj-ov-preview">${firstLine(d.markdown)}</span>
|
|
2904
|
+
</button>
|
|
2905
|
+
${ovDoc && ovDoc.type === ot.name && ovDoc.id === d.id ? renderOvDocInline(ot, d) : null}
|
|
2906
|
+
</div>` : html`
|
|
2907
|
+
<button class="pj-ov-item" key=${d.id} onClick=${() => openOvRec(ot, d)}>
|
|
2908
|
+
${d._draft ? html`<span class="badge badge-warn pj-mini">${t('organisms.draft') || 'draft'}</span>` : null}
|
|
2909
|
+
<span class="pj-ov-item-title">${String(d[PRIMARY_FIELD[ot.name] || 'title'] || d.summary || d.id || '')}</span>
|
|
2910
|
+
${d.status ? html`<span class="badge badge-info">${String(d.status)}</span>` : null}
|
|
2911
|
+
</button>`)}
|
|
2912
|
+
${items.length > 5 ? html`
|
|
2913
|
+
<button class="pj-ov-more" onClick=${() => pickTab('space:' + ot.name)}>${(t('organisms.ovShowAll') || 'Show all {n} →').replace('{n}', String(items.length))}</button>` : null}
|
|
2914
|
+
</div>` : null}
|
|
2915
|
+
</div>`;
|
|
2916
|
+
};
|
|
2917
|
+
|
|
2918
|
+
const renderOverview = () => {
|
|
2919
|
+
const recent = wsEvents.slice(0, 8);
|
|
2920
|
+
return html`
|
|
2921
|
+
<div class="pj-ov">
|
|
2922
|
+
${recent.length > 0 ? html`
|
|
2923
|
+
<div class="pj-ov-strip">
|
|
2924
|
+
<div class="pj-ov-strip-title">${t('organisms.ovRecent') || 'What happened here'}</div>
|
|
2925
|
+
${recent.map((e, i) => html`
|
|
2926
|
+
<button class="pj-ov-event" key=${i} onClick=${() => gotoEvent(e)}>
|
|
2927
|
+
<span class="pj-act-dot ${e.action}"></span>
|
|
2928
|
+
<span class="pj-ov-event-who">${shortActor(e.actor)}${e.agent ? html` <span class="pj-act-agent" title=${t('organisms.viaAgent') || 'via this agent'}>🤖 ${e.agent}</span>` : null}</span>
|
|
2929
|
+
<span class="pj-ov-event-act">${e.action === 'publish' ? (t('organisms.publishedVerb') || 'published') : (t('organisms.editedVerb') || 'edited')}</span>
|
|
2930
|
+
<span class="pj-ov-event-what">${e.mode === 'document' ? '📄' : '🗂'} ${(wsT('type.' + e.type) || e.type)}${' / '}${e.instance}</span>
|
|
2931
|
+
<span class="pj-ov-event-time">${relTime(e.at)}</span>
|
|
2932
|
+
</button>`)}
|
|
2933
|
+
</div>` : null}
|
|
2934
|
+
${allTypes.map(renderOvSection)}
|
|
2935
|
+
${allTypes.length === 0 ? html`<${EmptyState} text=${t('organisms.noneYet') || 'none yet'} />` : null}
|
|
2936
|
+
</div>`;
|
|
2937
|
+
};
|
|
2938
|
+
|
|
2684
2939
|
return html`
|
|
2685
2940
|
<div class="pj-ws">
|
|
2686
2941
|
<${ConfirmUI} />
|
|
@@ -2706,11 +2961,15 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
2706
2961
|
</div>` : null}
|
|
2707
2962
|
|
|
2708
2963
|
<div class="pj-org-tabs" role="tablist">
|
|
2709
|
-
${wsTabs.map(tb =>
|
|
2964
|
+
${wsTabs.map(tb => {
|
|
2965
|
+
const u = activeTab === tb.id ? 0 : unseenOf(tb.id);
|
|
2966
|
+
return html`
|
|
2710
2967
|
<button class="pj-org-tab ${activeTab === tb.id ? 'active' : ''}" role="tab" aria-selected=${activeTab === tb.id} key=${tb.id}
|
|
2711
2968
|
onClick=${() => pickTab(tb.id)}>
|
|
2712
2969
|
${(tb.label)}${tb.count !== null && tb.count !== undefined ? html`<span class="pj-org-tab-count">${tb.count}</span>` : null}
|
|
2713
|
-
|
|
2970
|
+
${u > 0 ? html`<span class="pj-org-tab-unseen" title=${t('organisms.unseenHint') || 'Changed since your last visit'}>${u}</span>` : null}
|
|
2971
|
+
</button>`;
|
|
2972
|
+
})}
|
|
2714
2973
|
<button class="pj-org-tab pj-ws-tab-add" title=${t('organisms.addDocSpaceTitle') || 'Add a document space'} onClick=${() => guardWsDirty(() => { setShowSettings(false); setShowSpaces(s => !s); })}>+</button>
|
|
2715
2974
|
</div>
|
|
2716
2975
|
|
|
@@ -2793,6 +3052,8 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
2793
3052
|
</div>
|
|
2794
3053
|
`}
|
|
2795
3054
|
|
|
3055
|
+
${activeTab === 'overview' ? renderOverview() : null}
|
|
3056
|
+
|
|
2796
3057
|
${activeSpace
|
|
2797
3058
|
? (!orgService.isMemorySpace(activeSpace)
|
|
2798
3059
|
? renderSpaceNotice(activeSpace)
|
|
@@ -2870,7 +3131,7 @@ function Workspace({ org, wsId, showToast, onBack, onBackToList }) {
|
|
|
2870
3131
|
`)}
|
|
2871
3132
|
</div>` : null}` : null}
|
|
2872
3133
|
|
|
2873
|
-
${activeTab === 'people' ? html`<${ParticipantsPanel} orgId=${orgId} wsId=${wsId} />` : null}
|
|
3134
|
+
${activeTab === 'people' ? html`<${ParticipantsPanel} orgId=${orgId} wsId=${wsId} showToast=${showToast} />` : null}
|
|
2874
3135
|
</div>
|
|
2875
3136
|
`;
|
|
2876
3137
|
}
|