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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file start.css
|
|
3
|
+
* @description Styles for the /v1/start diagnosis page (st- prefix): hero,
|
|
4
|
+
* question cards with big option buttons, the answer trail, the verdict card,
|
|
5
|
+
* the playbook checklist (code blocks, prompt block, doors) and print rules
|
|
6
|
+
* so "Print / save as PDF" emits just the playbook. Theme tokens only.
|
|
7
|
+
* @version-history
|
|
8
|
+
* v1.0.0 — 2026-06-11 — Initial: jira entrance styles + print rules.
|
|
9
|
+
* v1.0.1 — 2026-06-12 — Scope print rules to body.st-active: view CSS is global (spa.html
|
|
10
|
+
* preload), so the unscoped `body * { visibility: hidden }` blanked printing on every view
|
|
11
|
+
* (e.g. the public workspace viewer printed 14 empty pages).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
.st { max-width: 760px; margin: 0 auto; padding: 2rem 1.25rem 3rem; color: var(--text); }
|
|
15
|
+
|
|
16
|
+
/* ── Hero (intro phase) ── */
|
|
17
|
+
.st-hero { text-align: center; padding: 3rem 0 1.5rem; }
|
|
18
|
+
.st-h1 { font-size: 2.2rem; font-weight: 800; letter-spacing: -0.5px; margin: 0 0 .7rem; }
|
|
19
|
+
.st-hero-sub { color: var(--text-dim); font-size: 1.08rem; margin: 0 auto 1.6rem; max-width: 560px; }
|
|
20
|
+
.st-start-btn { font-size: 1.05rem; padding: .75rem 2rem; }
|
|
21
|
+
.st-hero-meta { margin-top: 1rem; font-size: .85rem; color: var(--muted); }
|
|
22
|
+
|
|
23
|
+
/* ── Compact header + trail (after intro) ── */
|
|
24
|
+
.st-header { margin-bottom: 1.5rem; }
|
|
25
|
+
.st-header-title { font-size: 1rem; font-weight: 700; color: var(--text-dim); margin-bottom: .6rem; }
|
|
26
|
+
.st-trail { display: flex; flex-wrap: wrap; gap: .4rem; }
|
|
27
|
+
.st-trail-chip { font-size: .78rem; padding: .25rem .6rem; border: 1px solid var(--border); border-radius: 999px; background: var(--card); color: var(--text-dim); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
28
|
+
|
|
29
|
+
/* ── Question card ── */
|
|
30
|
+
.st-q { padding: .5rem 0; }
|
|
31
|
+
.st-q-title { font-size: 1.45rem; font-weight: 800; margin: 0 0 1.2rem; }
|
|
32
|
+
.st-opts { display: flex; flex-direction: column; gap: .7rem; }
|
|
33
|
+
.st-opt { text-align: left; font-size: 1rem; padding: .9rem 1.1rem; border: 1px solid var(--border); border-radius: 12px; background: var(--card); color: var(--text); cursor: pointer; transition: border-color .15s, transform .05s; font-family: inherit; }
|
|
34
|
+
.st-opt:hover { border-color: var(--accent); }
|
|
35
|
+
.st-opt:active { transform: scale(.99); }
|
|
36
|
+
.st-back { margin-top: 1.2rem; background: none; border: none; color: var(--text-dim); cursor: pointer; font-size: .88rem; padding: .3rem 0; font-family: inherit; }
|
|
37
|
+
.st-back:hover { color: var(--text); text-decoration: underline; }
|
|
38
|
+
|
|
39
|
+
/* ── Verdict ── */
|
|
40
|
+
.st-verdict { text-align: center; padding: .5rem 0; }
|
|
41
|
+
.st-verdict-label { font-size: .85rem; text-transform: uppercase; letter-spacing: 2px; color: var(--muted); margin-bottom: .7rem; }
|
|
42
|
+
.st-verdict-card { border: 2px solid var(--accent); border-radius: 14px; background: var(--card); padding: 1.6rem 1.5rem; margin-bottom: 1.2rem; }
|
|
43
|
+
.st-verdict-name { font-size: 1.7rem; font-weight: 800; margin-bottom: .6rem; }
|
|
44
|
+
.st-verdict-desc { color: var(--text-dim); font-size: 1.02rem; margin: 0 auto .8rem; max-width: 540px; }
|
|
45
|
+
.st-verdict-score { font-size: .85rem; color: var(--muted); }
|
|
46
|
+
.st-verdict-actions { display: flex; gap: .8rem; justify-content: center; flex-wrap: wrap; margin-bottom: .9rem; }
|
|
47
|
+
.st-verdict-note { color: var(--text-dim); font-size: .92rem; margin: 0; }
|
|
48
|
+
|
|
49
|
+
/* ── Playbook ── */
|
|
50
|
+
.st-pb-head { margin-bottom: 1.5rem; }
|
|
51
|
+
.st-pb-title { font-size: 1.7rem; font-weight: 800; margin: 0 0 .4rem; }
|
|
52
|
+
.st-pb-sub { color: var(--text-dim); margin: 0 0 .5rem; }
|
|
53
|
+
.st-pb-tier { font-size: .9rem; font-weight: 700; color: var(--accent); margin: 0; }
|
|
54
|
+
.st-pb-section { margin-bottom: 1.4rem; border: 1px solid var(--border); border-radius: 12px; background: var(--card); padding: 1rem 1.2rem; }
|
|
55
|
+
.st-pb-section h3 { margin: 0 0 .7rem; font-size: 1.05rem; }
|
|
56
|
+
.st-pb-items { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .6rem; }
|
|
57
|
+
.st-pb-item { font-size: .95rem; }
|
|
58
|
+
.st-pb-check { display: flex; gap: .55rem; align-items: flex-start; cursor: pointer; }
|
|
59
|
+
.st-pb-check input { margin-top: .25rem; accent-color: var(--accent); flex: none; }
|
|
60
|
+
.st-pb-check input:checked + span { color: var(--text-dim); text-decoration: line-through; }
|
|
61
|
+
.st-pb-quote, .st-pb-prompt blockquote { margin: .3rem 0; padding: .6rem .9rem; border-left: 3px solid var(--accent); background: var(--bg-surface, var(--bg-dim)); border-radius: 6px; font-size: .92rem; }
|
|
62
|
+
.st-pb-code { display: flex; gap: .6rem; align-items: flex-start; margin-top: .4rem; }
|
|
63
|
+
.st-pb-code pre { flex: 1; margin: 0; padding: .65rem .9rem; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-surface, var(--bg-dim)); font-size: .82rem; overflow-x: auto; white-space: pre-wrap; word-break: break-all; }
|
|
64
|
+
.st-pb-copy { flex: none; }
|
|
65
|
+
.st-pb-prompt { margin-top: .4rem; }
|
|
66
|
+
.st-pb-prompt .btn-primary { margin-top: .5rem; }
|
|
67
|
+
.st-pb-link { color: var(--accent); font-weight: 700; text-decoration: none; font-size: .92rem; }
|
|
68
|
+
.st-pb-link:hover { text-decoration: underline; }
|
|
69
|
+
.st-pb-actions { display: flex; gap: .8rem; flex-wrap: wrap; align-items: center; margin: 1.4rem 0; }
|
|
70
|
+
|
|
71
|
+
/* ── Doors ── */
|
|
72
|
+
.st-doors { margin-top: 2rem; padding-top: 1.4rem; border-top: 1px solid var(--border); }
|
|
73
|
+
.st-doors-title { font-size: 1.15rem; margin: 0 0 .3rem; }
|
|
74
|
+
.st-doors-sub { color: var(--text-dim); font-size: .9rem; margin: 0 0 1rem; }
|
|
75
|
+
.st-doors-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .9rem; }
|
|
76
|
+
@media (max-width: 700px) { .st-doors-grid { grid-template-columns: 1fr; } }
|
|
77
|
+
.st-door { display: flex; flex-direction: column; gap: .3rem; border: 1px solid var(--border); border-radius: 12px; background: var(--card); padding: 1rem 1.1rem; text-decoration: none; color: var(--text); }
|
|
78
|
+
.st-door:hover { border-color: var(--accent); }
|
|
79
|
+
.st-door-name { font-weight: 700; }
|
|
80
|
+
.st-door-desc { font-size: .85rem; color: var(--text-dim); }
|
|
81
|
+
|
|
82
|
+
/* ── Footer ── */
|
|
83
|
+
.st-footer { margin-top: 2.5rem; padding-top: 1.2rem; border-top: 1px solid var(--border); text-align: center; font-size: .85rem; color: var(--text-dim); }
|
|
84
|
+
.st-footer a { color: var(--text-dim); }
|
|
85
|
+
.st-footer a:hover { color: var(--text); }
|
|
86
|
+
|
|
87
|
+
/* ── Print: emit only the playbook ──
|
|
88
|
+
* Scoped to body.st-active (added by start.js while the view is mounted): view CSS is preloaded
|
|
89
|
+
* globally in spa.html, so an unscoped `body *` rule here would blank printing on every view. */
|
|
90
|
+
@media print {
|
|
91
|
+
body.st-active * { visibility: hidden; }
|
|
92
|
+
body.st-active .st-print-area, body.st-active .st-print-area * { visibility: visible; }
|
|
93
|
+
body.st-active .st-print-area { position: absolute; top: 0; left: 0; width: 100%; }
|
|
94
|
+
body.st-active .st-no-print, body.st-active .st-no-print * { display: none !important; }
|
|
95
|
+
body.st-active .st-pb-section { break-inside: avoid; }
|
|
96
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -1,31 +1,46 @@
|
|
|
1
|
-
import { h } from 'preact';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
* @param {
|
|
10
|
-
* @param {
|
|
11
|
-
*
|
|
12
|
-
* @param {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
${
|
|
27
|
-
<button
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
import { h } from 'preact';
|
|
2
|
+
import { useState } from 'preact/hooks';
|
|
3
|
+
import htm from 'htm';
|
|
4
|
+
const html = htm.bind(h);
|
|
5
|
+
import { t } from '/js/i18n.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TagCloud — clickable tag filter pills.
|
|
9
|
+
* @param {Object} props
|
|
10
|
+
* @param {string[]} props.tags — available tags. Pre-sorted order is respected when
|
|
11
|
+
* `limit` is set (pass most-used first); otherwise sorted alphabetically.
|
|
12
|
+
* @param {Set<string>} props.selected — currently active filter tags
|
|
13
|
+
* @param {(tag: string) => void} props.onToggle — called when a tag is clicked
|
|
14
|
+
* @param {() => void} props.onClear — called when clear button is clicked
|
|
15
|
+
* @param {number} [props.limit] — show only the first N tags behind a "show all" toggle.
|
|
16
|
+
* Selected tags always stay visible so an active filter can't hide its own pill.
|
|
17
|
+
*/
|
|
18
|
+
export default function TagCloud({ tags, selected, onToggle, onClear, limit }) {
|
|
19
|
+
const [showAll, setShowAll] = useState(false);
|
|
20
|
+
if (!tags || tags.length === 0) return null;
|
|
21
|
+
const ordered = limit ? [...tags] : [...tags].sort();
|
|
22
|
+
const capped = limit && !showAll && ordered.length > limit;
|
|
23
|
+
const visible = capped ? ordered.filter((tag, i) => i < limit || selected.has(tag)) : ordered;
|
|
24
|
+
return html`
|
|
25
|
+
<div class="tag-cloud">
|
|
26
|
+
${visible.map(tag => html`
|
|
27
|
+
<button key=${tag}
|
|
28
|
+
class="tag-pill ${selected.has(tag) ? 'active' : ''}"
|
|
29
|
+
onClick=${() => onToggle(tag)}>
|
|
30
|
+
${tag}
|
|
31
|
+
</button>
|
|
32
|
+
`)}
|
|
33
|
+
${capped && html`
|
|
34
|
+
<button class="tag-pill tag-more" onClick=${() => setShowAll(true)}>
|
|
35
|
+
${(t('profile.memory.showAllTags') || 'Show all ({n})').replace('{n}', String(ordered.length))}
|
|
36
|
+
</button>
|
|
37
|
+
`}
|
|
38
|
+
${limit && showAll && ordered.length > limit && html`
|
|
39
|
+
<button class="tag-pill tag-more" onClick=${() => setShowAll(false)}>${t('profile.memory.fewerTags') || 'Show fewer'}</button>
|
|
40
|
+
`}
|
|
41
|
+
${selected.size > 0 && html`
|
|
42
|
+
<button class="tag-pill tag-clear" onClick=${onClear}>✕ ${t('tags.clear') || 'Clear'}</button>
|
|
43
|
+
`}
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
@@ -29,8 +29,8 @@ export default function TagEditor({ tags, onSave, maxTags = 20 }) {
|
|
|
29
29
|
<div class="tag-editor">
|
|
30
30
|
<div class="tag-editor-pills">
|
|
31
31
|
${tags.map(tag => html`
|
|
32
|
-
<span class="tag-pill
|
|
33
|
-
${tag}
|
|
32
|
+
<span class="tag-pill tag-removable" key=${tag} onClick=${() => removeTag(tag)}>
|
|
33
|
+
${tag} <span class="tag-x">\u2715</span>
|
|
34
34
|
</span>
|
|
35
35
|
`)}
|
|
36
36
|
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file recents.js
|
|
3
|
+
* @description "Continue" list backing store — the last opened things (workspaces, apps,
|
|
4
|
+
* organisms, …) across types, so the profile home can offer one-click returns with real
|
|
5
|
+
* display names. Device-local by design (localStorage): what you opened HERE.
|
|
6
|
+
* @structure
|
|
7
|
+
* - recordRecent(item) — upsert by type+id to the front, capped at 12
|
|
8
|
+
* - listRecents(n) — newest first
|
|
9
|
+
* @usage
|
|
10
|
+
* import { recordRecent, listRecents } from '/js/recents.js';
|
|
11
|
+
* recordRecent({ type: 'workspace', id: orgId + '/' + wsId, label: wsName, sub: orgName, data: { orgId, wsId } });
|
|
12
|
+
* @version-history
|
|
13
|
+
* v1.0.0 — 2026-06-10 — Initial: localStorage-backed cross-type recents for the home dashboard.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const KEY = 'aimeat.recents';
|
|
17
|
+
const CAP = 12;
|
|
18
|
+
|
|
19
|
+
function readAll() {
|
|
20
|
+
try {
|
|
21
|
+
const raw = localStorage.getItem(KEY);
|
|
22
|
+
const list = raw ? JSON.parse(raw) : [];
|
|
23
|
+
return Array.isArray(list) ? list : [];
|
|
24
|
+
} catch { return []; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Record a visit. item: { type, id, label, sub?, data? } — type+id is the identity. */
|
|
28
|
+
export function recordRecent(item) {
|
|
29
|
+
if (!item || !item.type || !item.id || !item.label) return;
|
|
30
|
+
try {
|
|
31
|
+
const list = readAll().filter(x => !(x.type === item.type && x.id === item.id));
|
|
32
|
+
list.unshift({ type: item.type, id: item.id, label: item.label, sub: item.sub || '', data: item.data || {}, at: new Date().toISOString() });
|
|
33
|
+
localStorage.setItem(KEY, JSON.stringify(list.slice(0, CAP)));
|
|
34
|
+
} catch { /* recents are a convenience — never break the caller */ }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Newest-first recents, capped at n. */
|
|
38
|
+
export function listRecents(n = 5) {
|
|
39
|
+
return readAll().slice(0, n);
|
|
40
|
+
}
|
|
@@ -269,3 +269,9 @@ export const getAdminAgentTasks = (params = {}) => {
|
|
|
269
269
|
|
|
270
270
|
// ── Sharing Groups (admin) ──
|
|
271
271
|
export const getAdminSharingGroups = () => apiGet('/v1/admin/sharing-groups');
|
|
272
|
+
|
|
273
|
+
// ── Subdomain mappings (operator) ──
|
|
274
|
+
export const getSubdomainSites = () => apiGet('/v1/admin/subdomains');
|
|
275
|
+
export const createSubdomainSite = (body) => apiPost('/v1/admin/subdomains', body);
|
|
276
|
+
export const updateSubdomainSite = (sub, updates) => apiPatch(`/v1/admin/subdomains/${encodeURIComponent(sub)}`, updates);
|
|
277
|
+
export const deleteSubdomainSite = (sub) => apiDelete(`/v1/admin/subdomains/${encodeURIComponent(sub)}`);
|
|
@@ -11,6 +11,40 @@ export async function listAgents(owner) {
|
|
|
11
11
|
return owner ? list.filter(a => a.owner === owner) : list;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/** Workspace-contract advertising via owner-managed agent tags (colon-free — the server tag
|
|
15
|
+
* pattern allows only [a-z0-9._-]): the tag 'workspace-contract' marks an agent as a contract
|
|
16
|
+
* provider, and 'contract.<name>' tags name the contract(s) it serves. Convention documented in
|
|
17
|
+
* docs/agent-workspace-contracts.md; agents set tags via aimeat_agent_tags_set / PATCH tags. */
|
|
18
|
+
export const offersWorkspaceContract = (a) => (a?.tags || []).includes('workspace-contract');
|
|
19
|
+
|
|
20
|
+
/** The contract names an agent advertises ('contract.<name>' tags, prefix stripped). */
|
|
21
|
+
export const contractNamesOf = (a) => (a?.tags || [])
|
|
22
|
+
.filter(tg => String(tg).startsWith('contract.'))
|
|
23
|
+
.map(tg => String(tg).slice('contract.'.length));
|
|
24
|
+
|
|
25
|
+
/** One-click contract adoption: queue the agreed adopt-contract TASK for one of the owner's
|
|
26
|
+
* agents. The agent recognises it from scope[kind]='adopt-contract' (NOT the title), joins the
|
|
27
|
+
* organism if needed, provisions its contract's spaces itself (workspace_update add_spaces from
|
|
28
|
+
* its OWN contract declaration — §2 of the guide), and completes the task with what was added.
|
|
29
|
+
* Convention: docs/agent-workspace-contracts.md §7c — agreed with the crewaimeat side 2026-06-10. */
|
|
30
|
+
export async function adoptContractTask(agentName, { organismId, ws, contract }) {
|
|
31
|
+
const { createTask } = await import('/js/services/agent-tasks.js');
|
|
32
|
+
return createTask(agentName, {
|
|
33
|
+
title: `adopt-contract: ${contract || 'workspace-contract'} → ${ws}`,
|
|
34
|
+
description: `Adopt your '${contract || ''}' contract into workspace ${ws} of organism ${organismId}: join the organism if needed, provision the contract's spaces (workspace_update add_spaces from your own contract declaration), then complete this task with what was added.`,
|
|
35
|
+
// 'queued' is REQUIRED: without it the task is created as a draft and never auto-activates.
|
|
36
|
+
// A task-runner-mode agent flips queued → active immediately (push over the tunnel); other
|
|
37
|
+
// modes keep the standard queued → owner-start gate. (crewaimeat live-test finding 2026-06-10.)
|
|
38
|
+
status: 'queued',
|
|
39
|
+
scope: [
|
|
40
|
+
{ name: 'kind', value: 'adopt-contract', type: 'text' },
|
|
41
|
+
{ name: 'organism_id', value: organismId, type: 'text' },
|
|
42
|
+
{ name: 'ws', value: ws, type: 'text' },
|
|
43
|
+
...(contract ? [{ name: 'contract', value: contract, type: 'text' }] : []),
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
14
48
|
/** List chat session agents (name starts with 'session-'). */
|
|
15
49
|
export async function listChatSessions(owner) {
|
|
16
50
|
const agents = await listAgents(owner);
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file offers.js
|
|
3
|
+
* @description Frontend service for the Agent Offers surface ("what can I do with my agents"). Reads
|
|
4
|
+
* the owner aggregate feed + per-agent offers, and the mode-aware Ask: a schedule-born offer triggers
|
|
5
|
+
* its schedule; a task-runner/autonomous agent runs a task; an interactive/workstation agent gets a
|
|
6
|
+
* paste-ready prompt (AIMEAT's prompt-driven flow). See docs/plans/2026-06-12-agent-offers-*.md.
|
|
7
|
+
* @structure listOffers / getAgentOffers / publishOffers · ask(agentEntry, offer, inputs) · buildAskPrompt
|
|
8
|
+
* @usage import * as offers from '/js/services/offers.js';
|
|
9
|
+
* @version-history
|
|
10
|
+
* v1.0.0 -- 2026-06-12 -- Initial: Do feed + mode-aware Ask (run / copy-prompt / schedule-trigger).
|
|
11
|
+
* v1.1.0 -- 2026-06-12 -- Billable offers: setOfferBilling (patch one offer's price + visibility).
|
|
12
|
+
* v1.2.0 -- 2026-06-12 -- Inbox fixes: deliverable read from the AGENT namespace (prefix + task-tag
|
|
13
|
+
* fallback, returns a string); Ask task title carries the user's request (findable in the queue).
|
|
14
|
+
* v1.3.0 -- 2026-06-12 -- Offer task carries ONLY the user's request (title + description); the offer
|
|
15
|
+
* is referenced via scope.offer_id/offer_title — no more restuffing offer.ask/example (which made
|
|
16
|
+
* the agent treat its own boilerplate as the request).
|
|
17
|
+
*/
|
|
18
|
+
import { apiGet, apiPost, apiPut } from '/js/api.js';
|
|
19
|
+
import { copyToClipboard } from '/js/utils.js';
|
|
20
|
+
import { createTask, rateTask } from '/js/services/agent-tasks.js';
|
|
21
|
+
|
|
22
|
+
/** The goal-first feed: every agent's offers + mode/availability. */
|
|
23
|
+
export async function listOffers() {
|
|
24
|
+
return apiGet('/v1/offers');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** One agent's published offers. */
|
|
28
|
+
export async function getAgentOffers(name) {
|
|
29
|
+
return apiGet(`/v1/agents/${encodeURIComponent(name)}/offers`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Publish/replace an agent's offers (agent self or owner). */
|
|
33
|
+
export async function publishOffers(name, doc) {
|
|
34
|
+
return apiPut(`/v1/agents/${encodeURIComponent(name)}/offers`, doc);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Owner edits ONE offer's billing fields (price + visibility), preserving the rest of the doc.
|
|
38
|
+
* Re-reads the agent's current offers first (avoid clobbering a stale feed), patches, re-publishes.
|
|
39
|
+
* `price` is `{ morsels, unit }` or `null` (not for sale); `visibility` is private|unlisted|public. */
|
|
40
|
+
export async function setOfferBilling(name, offerId, { price, visibility }) {
|
|
41
|
+
const cur = await getAgentOffers(name);
|
|
42
|
+
const offers = (cur?.data?.offers || []).map(o => o.id === offerId
|
|
43
|
+
? { ...o, ...(price !== undefined ? { price } : {}), ...(visibility !== undefined ? { visibility } : {}) }
|
|
44
|
+
: o);
|
|
45
|
+
return publishOffers(name, { offers });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The Inbox feed: everything that came back across all agents (non-draft tasks, newest first). */
|
|
49
|
+
export async function listDeliverables() {
|
|
50
|
+
return apiGet('/v1/deliverables');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Fetch a task's deliverable from the AGENT's memory namespace (owner→agent read).
|
|
54
|
+
* Deliverables live under the agent's GAII, not the owner's, so we read via
|
|
55
|
+
* `?agent=<gaii>`. Primary handle is the recorded `deliverableKey`; fallback is the
|
|
56
|
+
* `task:<id>` tag the runner stamps on every write (works even when the agent never
|
|
57
|
+
* set deliverableKey — e.g. CrewAI agents that only publish to memory). Returns the
|
|
58
|
+
* deliverable value as a string, or null if nothing was found. Mirrors the proven
|
|
59
|
+
* read in agents-tasks-subtab.js. */
|
|
60
|
+
export async function getDeliverableContent({ agentGaii, deliverableKey, taskId }) {
|
|
61
|
+
if (!agentGaii) return null;
|
|
62
|
+
const enc = encodeURIComponent;
|
|
63
|
+
const reqs = [];
|
|
64
|
+
if (deliverableKey) reqs.push(apiGet(`/v1/memory?agent=${enc(agentGaii)}&prefix=${enc(deliverableKey)}&per_page=5`).catch(() => null));
|
|
65
|
+
if (taskId) reqs.push(apiGet(`/v1/memory?agent=${enc(agentGaii)}&tags=${enc('task:' + taskId)}&per_page=50`).catch(() => null));
|
|
66
|
+
const results = await Promise.all(reqs);
|
|
67
|
+
const items = [];
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
for (const r of results) {
|
|
70
|
+
const list = r?.data?.items || r?.data || [];
|
|
71
|
+
for (const it of (Array.isArray(list) ? list : [])) {
|
|
72
|
+
if (it.key && !seen.has(it.key)) { seen.add(it.key); items.push(it); }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!items.length) return null;
|
|
76
|
+
const len = (v) => (typeof v === 'string' ? v.length : 0);
|
|
77
|
+
// Prefer the exact deliverableKey, then an *output key, then the longest text value.
|
|
78
|
+
const pick = items.find(i => i.key === deliverableKey)
|
|
79
|
+
|| items.find(i => /(latest_)?output$/.test(i.key))
|
|
80
|
+
|| items.slice().sort((a, b) => len(b.value) - len(a.value))[0];
|
|
81
|
+
const v = pick?.value;
|
|
82
|
+
if (v == null) return null;
|
|
83
|
+
return typeof v === 'string' ? v : JSON.stringify(v, null, 2);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Rate a done task's deliverable via the LOCKED rate endpoint (→ agents.<agent>.statistics.*).
|
|
87
|
+
* Owner ratings bypass source-grounding; we still send source_grounded:true (the owner saw it). */
|
|
88
|
+
export async function rateDeliverable(agent, taskId, { stars, comment, context = 'other' }) {
|
|
89
|
+
return rateTask(agent, taskId, { stars, context, comment, source_grounded: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Compose the paste-ready prompt for an interactive/workstation agent (prompt-driven Ask). */
|
|
93
|
+
export function buildAskPrompt(agentEntry, offer, inputs) {
|
|
94
|
+
const lines = [
|
|
95
|
+
`You are the agent "${agentEntry.agent}". I'm asking you to perform one of your offers.`,
|
|
96
|
+
``,
|
|
97
|
+
`OFFER: ${offer.title}`,
|
|
98
|
+
`WHAT I'M ASKING: ${offer.ask}`,
|
|
99
|
+
];
|
|
100
|
+
if (offer.example) lines.push(`EXAMPLE OF A REQUEST LIKE THIS: ${offer.example}`);
|
|
101
|
+
if (inputs && String(inputs).trim()) lines.push(``, `MY REQUEST: ${String(inputs).trim()}`);
|
|
102
|
+
if (offer.deliverable?.format) lines.push(``, `DELIVER: a ${offer.deliverable.format}${offer.deliverable.location?.space ? ` in ${offer.deliverable.location.space}` : ''}.`);
|
|
103
|
+
lines.push(``, `Do exactly this offer and nothing outside its scope. When done, write the deliverable and tell me where it is.`);
|
|
104
|
+
return lines.join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function scheduleIdOf(offer) {
|
|
108
|
+
const sb = offer?.availability?.scheduleBorn;
|
|
109
|
+
if (!sb) return null;
|
|
110
|
+
return typeof sb === 'object' ? (sb.scheduleId || null) : null; // string form is a human label only
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Mode-aware Ask. Branch order: schedule-born first (independent of mode), then by agent mode.
|
|
115
|
+
* Returns one of:
|
|
116
|
+
* { kind: 'triggered' } — schedule-born → ran the schedule now
|
|
117
|
+
* { kind: 'task', taskId, agent } — task-runner/autonomous → created a queued task
|
|
118
|
+
* { kind: 'prompt', copied: true, prompt } — interactive/workstation → copied a paste-ready prompt
|
|
119
|
+
*/
|
|
120
|
+
export async function ask(agentEntry, offer, inputs) {
|
|
121
|
+
const scheduleId = scheduleIdOf(offer);
|
|
122
|
+
if (scheduleId) {
|
|
123
|
+
const r = await apiPost(`/v1/schedules/${encodeURIComponent(scheduleId)}/trigger`, {});
|
|
124
|
+
return { kind: 'triggered', ok: r?.ok !== false, error: r?.error };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const mode = agentEntry.mode || 'interactive';
|
|
128
|
+
if (mode === 'task-runner' || mode === 'autonomous') {
|
|
129
|
+
// The agent ALREADY owns the offer (it published it). Do NOT restuff offer.ask/example into the
|
|
130
|
+
// task — that makes the agent treat its own boilerplate as the request (e.g. jokes ABOUT four
|
|
131
|
+
// comedians instead of jokes on the topic). The task carries ONLY the user's request as title +
|
|
132
|
+
// description; the offer is referenced structurally via scope (the runtime resolves it by offer_id).
|
|
133
|
+
const reqText = inputs ? String(inputs).trim() : '';
|
|
134
|
+
const title = reqText ? (reqText.length > 80 ? reqText.slice(0, 80) + '…' : reqText) : offer.title;
|
|
135
|
+
const r = await createTask(agentEntry.agent, {
|
|
136
|
+
title,
|
|
137
|
+
description: reqText,
|
|
138
|
+
status: 'queued', // REQUIRED so a task-runner auto-activates it (other modes keep the owner-start gate)
|
|
139
|
+
scope: [
|
|
140
|
+
{ name: 'kind', value: 'offer', type: 'text' },
|
|
141
|
+
{ name: 'offer_id', value: offer.id, type: 'text' },
|
|
142
|
+
{ name: 'offer_title', value: offer.title, type: 'text' },
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
return { kind: 'task', taskId: r?.data?.task?.id || r?.data?.id, agent: agentEntry.agent, ok: r?.ok !== false, error: r?.error };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// interactive / workstation → prompt-driven
|
|
149
|
+
const prompt = buildAskPrompt(agentEntry, offer, inputs);
|
|
150
|
+
await copyToClipboard(prompt);
|
|
151
|
+
return { kind: 'prompt', copied: true, prompt };
|
|
152
|
+
}
|
|
@@ -27,6 +27,7 @@ export const createPackage = (data) => apiPost('/v1/packages', data);
|
|
|
27
27
|
export const createVersion = (groupId, data) => apiPost(`/v1/packages/${enc(groupId)}/versions`, data);
|
|
28
28
|
export const updatePackage = (groupId, data) => apiPatch(`/v1/packages/${enc(groupId)}`, data);
|
|
29
29
|
export const archiveVersion = (groupId, version) => apiDelete(`/v1/packages/${enc(groupId)}/versions/${enc(version)}`);
|
|
30
|
+
export const archivePackageGroup = (groupId) => apiDelete(`/v1/packages/${enc(groupId)}`);
|
|
30
31
|
|
|
31
32
|
// ── Instances ──
|
|
32
33
|
export const listInstances = (params) => apiGet('/v1/instances' + buildQuery(params));
|
package/dist/public/spa.html
CHANGED
|
@@ -67,9 +67,11 @@
|
|
|
67
67
|
<!-- View CSS preloaded so they are ready before any view renders (prevents FOUC) -->
|
|
68
68
|
<!-- hrefs are stamped with ?v=BUILD_ID at serve time by portal.ts serveSpa() -->
|
|
69
69
|
<link rel="stylesheet" href="/css/views/portal.css">
|
|
70
|
+
<link rel="stylesheet" href="/css/views/landing.css">
|
|
70
71
|
<link rel="stylesheet" href="/css/views/portal-classic.css">
|
|
71
72
|
<link rel="stylesheet" href="/css/views/portal-dev.css">
|
|
72
73
|
<link rel="stylesheet" href="/css/views/profile.css">
|
|
74
|
+
<link rel="stylesheet" href="/css/views/offers.css">
|
|
73
75
|
<link rel="stylesheet" href="/css/views/foundry.css">
|
|
74
76
|
<link rel="stylesheet" href="/css/views/calibrator.css">
|
|
75
77
|
<link rel="stylesheet" href="/css/views/hobbies.css">
|
|
@@ -86,6 +88,7 @@
|
|
|
86
88
|
<link rel="stylesheet" href="/css/views/admin-agent-integration.css">
|
|
87
89
|
<link rel="stylesheet" href="/css/views/public-knowledge-viewer.css">
|
|
88
90
|
<link rel="stylesheet" href="/css/views/public-workspace-viewer.css">
|
|
91
|
+
<link rel="stylesheet" href="/css/views/start.css">
|
|
89
92
|
<script type="importmap">
|
|
90
93
|
{
|
|
91
94
|
"imports": {
|
|
@@ -98,6 +101,7 @@
|
|
|
98
101
|
"/js/format.js": "/js/format.js",
|
|
99
102
|
"/js/api.js": "/js/api.js",
|
|
100
103
|
"/js/hooks.js": "/js/hooks.js",
|
|
104
|
+
"/js/recents.js": "/js/recents.js",
|
|
101
105
|
"/js/services/admin.js": "/js/services/admin.js",
|
|
102
106
|
"/js/services/agents.js": "/js/services/agents.js",
|
|
103
107
|
"/js/services/apps.js": "/js/services/apps.js",
|
|
@@ -151,6 +155,7 @@
|
|
|
151
155
|
"/js/services/memory.js": "/js/services/memory.js",
|
|
152
156
|
"/js/services/nodes.js": "/js/services/nodes.js",
|
|
153
157
|
"/js/services/organisms.js": "/js/services/organisms.js",
|
|
158
|
+
"/js/services/offers.js": "/js/services/offers.js",
|
|
154
159
|
"/js/services/packages.js": "/js/services/packages.js",
|
|
155
160
|
"/js/services/security.js": "/js/services/security.js",
|
|
156
161
|
"/js/services/stats.js": "/js/services/stats.js",
|
|
@@ -165,6 +170,7 @@
|
|
|
165
170
|
"/components/Mermaid.js": "/components/Mermaid.js",
|
|
166
171
|
"/components/NotificationBell.js": "/components/NotificationBell.js",
|
|
167
172
|
"/components/CopyButton.js": "/components/CopyButton.js",
|
|
173
|
+
"/components/ContactCard.js": "/components/ContactCard.js",
|
|
168
174
|
"/components/Spinner.js": "/components/Spinner.js",
|
|
169
175
|
"/components/EmptyState.js": "/components/EmptyState.js",
|
|
170
176
|
"/components/ToggleSwitch.js": "/components/ToggleSwitch.js",
|
|
@@ -255,7 +261,14 @@
|
|
|
255
261
|
// insufficient — modules stay in the module registry for the full session).
|
|
256
262
|
const B = window.__B || '';
|
|
257
263
|
const ROUTES = {
|
|
258
|
-
|
|
264
|
+
// Landing/portal split (2026-06-10): the route serves the logged-out landing;
|
|
265
|
+
// logged-in visitors are forwarded to /v1/profile by the view itself. The old
|
|
266
|
+
// portal view stays reachable at /v1/classic (prompt boxes / "Build").
|
|
267
|
+
'/v1/portal': () => import('/views/landing.js' + B),
|
|
268
|
+
'/v1/pricing': () => import('/views/pricing.js' + B),
|
|
269
|
+
'/v1/start': () => import('/views/start.js' + B),
|
|
270
|
+
'/v1/how-it-works': () => import('/views/how-it-works.js' + B),
|
|
271
|
+
'/v1/business': () => import('/views/business.js' + B),
|
|
259
272
|
'/v1/profile': () => import('/views/profile.js' + B),
|
|
260
273
|
'/v1/guides': () => import('/views/guides.js' + B),
|
|
261
274
|
'/v1/hobbies': () => import('/views/hobbies.js' + B),
|
|
@@ -347,6 +360,14 @@
|
|
|
347
360
|
// Only intercept internal /v1/ links
|
|
348
361
|
if (!href || !href.startsWith('/v1/')) return;
|
|
349
362
|
if (e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
363
|
+
// Respect explicit new-tab / new-window targets — without this, an
|
|
364
|
+
// <a href="/v1/apps/.../launch?mode=inline" target="_blank"> from a
|
|
365
|
+
// child component would be hijacked, navigated in the same tab via
|
|
366
|
+
// matchRoute (which doesn't know /v1/apps/...), and dump the user
|
|
367
|
+
// back on the landing page.
|
|
368
|
+
const target = a.getAttribute('target');
|
|
369
|
+
if (target && target !== '_self' && target !== '') return;
|
|
370
|
+
if (a.hasAttribute('download')) return;
|
|
350
371
|
e.preventDefault();
|
|
351
372
|
navigate(href);
|
|
352
373
|
};
|
|
@@ -438,6 +459,10 @@
|
|
|
438
459
|
|
|
439
460
|
const handleNav = (e, path) => {
|
|
440
461
|
e.preventDefault();
|
|
462
|
+
// Mark in-app navigation: the landing redirects logged-in users to their Home
|
|
463
|
+
// ONLY on direct arrival (bookmark/external link), never when they deliberately
|
|
464
|
+
// navigated here from inside the app (e.g. the brand link).
|
|
465
|
+
try { sessionStorage.setItem('aimeat.in-app', '1'); } catch {}
|
|
441
466
|
navigate(path);
|
|
442
467
|
};
|
|
443
468
|
|
|
@@ -456,7 +481,12 @@
|
|
|
456
481
|
<button class="topnav-burger" aria-label="Menu" aria-expanded=${menuOpen ? 'true' : 'false'}
|
|
457
482
|
onClick=${() => setMenuOpen(o => !o)}>☰</button>
|
|
458
483
|
<div class="topnav-menu ${menuOpen ? 'open' : ''}">
|
|
459
|
-
|
|
484
|
+
<!-- "Try it" leads to the landing's path cards + app gallery — an empty
|
|
485
|
+
Welcome Board on /v1/classic was the weakest possible first experience.
|
|
486
|
+
Classic stays reachable deeper (landing card B "Start building"). -->
|
|
487
|
+
<a href="/v1/portal" onClick=${(e) => { handleNav(e, '/v1/portal'); setMenuOpen(false); }} class="${currentPath === '/v1/portal' ? 'active' : ''}">${t('nav.try')}</a>
|
|
488
|
+
<a href="/v1/how-it-works" onClick=${(e) => { handleNav(e, '/v1/how-it-works'); setMenuOpen(false); }} class="${currentPath === '/v1/how-it-works' ? 'active' : ''}">${t('nav.howItWorks') || 'How it works'}</a>
|
|
489
|
+
<a href="/v1/business" onClick=${(e) => { handleNav(e, '/v1/business'); setMenuOpen(false); }} class="${currentPath === '/v1/business' ? 'active' : ''}">${t('nav.business') || 'For your business'}</a>
|
|
460
490
|
<a href="/v1/portal?view=dev" onClick=${(e) => { handleNav(e, '/v1/portal?view=dev'); setMenuOpen(false); }}>${t('nav.devView')}</a>
|
|
461
491
|
<a href="/v1/help" onClick=${(e) => { handleNav(e, '/v1/help'); setMenuOpen(false); }} class="${currentPath === '/v1/help' ? 'active' : ''}">${t('nav.help')}</a>
|
|
462
492
|
${session && html`
|