`).join('');
}
function updateMemoryPreview() {
const el = document.getElementById('memory-preview');
const companion = document.getElementById('companion-preview');
if (!el || !companion) return;
const enabledModules = yucaState.modules.filter(item => item.enabled).map(item => item.name).join(' · ');
if (!yucaState.memory.length) {
el.textContent = 'Research, Goals, Portfolio, Alerts, AI Copilot, Notifications, and Admin should behave like installable modules with clear inputs, actions, and permissions.';
companion.textContent = 'Yuca should show workflow runs, last tool call, failed integrations, alert history, and role gating so the app feels explainable instead of mysterious.';
return;
}
const last = yucaState.memory[yucaState.memory.length - 1];
el.innerHTML = `Last intent: ${escapeHtml(last.intent)} Enabled modules: ${escapeHtml(enabledModules)} Data objects: ${escapeHtml(yucaState.dataObjects.join(', '))}`;
companion.innerHTML = `Mode: ${yucaState.builderMode ? 'Builder' : 'Usage'} Roles: ${escapeHtml(yucaState.roles.join(' → '))} Surface: ${escapeHtml(yucaState.currentPage)}`;
}
function updateBridgePreview(detail = 'Gold Days should be data-model first: users, accounts, positions, goals, alerts, AI sessions, and research items feed every page and every Yuca answer.') {
const el = document.getElementById('bridge-preview');
if (el) el.innerHTML = detail;
}
function renderSessionTimeline() {
const el = document.getElementById('session-timeline');
if (!el) return;
if (!yucaState.timeline.length) {
el.innerHTML = '
`;
}
function renderExecutionCenter() {
const el = document.getElementById('execution-center');
if (!el) return;
const ex = yucaState.executionCenter;
el.innerHTML = `
${escapeHtml(ex.mode)} execution
${ex.queueMode ? 'queue mode' : 'direct mode'}
Policy: ${escapeHtml(ex.activationPolicy)}
Timeout: ${escapeHtml(String(ex.timeoutMs))} ms · AI timeout: ${escapeHtml(String(ex.aiTimeoutMs))} ms · Concurrency: ${escapeHtml(String(ex.concurrency))}
`;
}
function renderSubflowLibrary() {
const el = document.getElementById('subflow-library');
if (!el) return;
el.innerHTML = yucaState.subflowLibrary.map(flow => `
${escapeHtml(flow.name)}
sub-flow
Contract: ${escapeHtml(flow.io)}
Used by: ${escapeHtml(flow.usedBy.join(', '))}
${escapeHtml(flow.notes)}
`).join('');
}
function renderConnectorRegistry() {
const el = document.getElementById('connector-registry');
if (!el) return;
el.innerHTML = yucaState.connectorRegistry.map(conn => `
`).join('')}
`;
}
function renderScenarioRegistry() {
const el = document.getElementById('yuca-scenario-panel');
if (!el) return;
el.innerHTML = yucaState.scenarioRegistry.map(scn => `
${escapeHtml(scn.name)}
${escapeHtml(scn.route)}
${escapeHtml(scn.status)}
${escapeHtml(scn.coverage)}
`).join('');
}
function renderValidationPanels() {
const el = document.getElementById('yuca-validation-panel');
if (!el) return;
const cov = yucaState.coverageBoard;
el.innerHTML = `
Coverage board
Profiles · ${escapeHtml(cov.profiles)}
Workflows · ${escapeHtml(cov.workflows)}
Connectors · ${escapeHtml(cov.connectors)}
Approvals · ${escapeHtml(cov.approvals)}
Knowledge · ${escapeHtml(cov.knowledge)}
Regressions · ${escapeHtml(cov.regressions)}
${yucaState.validationGate.map(v => `
${escapeHtml(v.label)}
${escapeHtml(v.mode)}
${escapeHtml(v.result)}
${escapeHtml(v.detail)}
`).join('')}
`;
}
function keepModelWarm(modelId) {
const target = yucaState.modelCatalog.find(item => item.id === modelId) || yucaState.runnerSessions.find(item => item.model === modelId);
if (!target) return;
if ('warm' in target) target.warm = true;
const run = yucaState.runnerSessions.find(item => item.model === modelId);
if (run) { run.state = 'loaded'; run.ttl = yucaState.runtime.keepAlive; }
renderRuntimePanel();
renderRuntimeSessions();
renderWorkspaceProfiles();
renderKnowledgeVault();
renderWorkspaceNotes();
renderPipelineRail();
renderFunctionsPanel();
renderExecutionCenter();
renderSubflowLibrary();
renderConnectorRegistry();
renderRunHistory();
updateYucaStatus('Runner pool', `${modelId} pinned in memory`);
rememberEvent('runtime', `${modelId} keep_alive pinned to ${yucaState.runtime.keepAlive}`);
showToast(`${modelId} kept warm`);
}
function expireModel(modelId) {
const run = yucaState.runnerSessions.find(item => item.model === modelId);
if (run) { run.state = 'idle'; run.ttl = '0'; }
const catalog = yucaState.modelCatalog.find(item => item.id === modelId);
if (catalog && 'warm' in catalog) catalog.warm = false;
renderRuntimePanel();
renderRuntimeSessions();
rememberEvent('runtime', `${modelId} unloaded`);
showToast(`${modelId} unloaded`);
}
function promoteBlob(digest) {
const blob = yucaState.blobs.find(item => item.digest === digest);
if (!blob) return;
updateBridgePreview(`Blob-backed artifacts let Yuca reuse outputs cleanly: ${blob.name} can be promoted into a dashboard card, a downloadable report, or a cached AI artifact without recomputing it.`);
openCanvasScene('artifact-promotion', `${blob.name} is being framed as a reusable site asset.`);
rememberEvent('blob', `${blob.name} promoted into builder canvas`);
showToast(`${blob.name} promoted`);
}
function inspectBlob(digest) {
const blob = yucaState.blobs.find(item => item.digest === digest);
if (!blob) return;
addChatMessage('ai', `Artifact inspection: ${escapeHtml(blob.name)}
This blob would live behind a digest address so the site can treat AI outputs like assets: cached summaries, generated cards, embeddings, or multimodal attachments.`);
rememberEvent('blob', `${blob.name} inspected`);
}
function updateChannelInbox() {
const el = document.getElementById('channel-inbox');
if (!el) return;
el.innerHTML = yucaState.modules.map(mod => `
`).join('');
}
function focusModule(moduleId) {
const mod = yucaState.modules.find(item => item.id === moduleId);
if (!mod) return;
if (moduleId === 'yuca') goToPage('ai', 'Opened Yuca Workspace Control');
else if (moduleId === 'portfolio') goToPage('landing', 'Opened portfolio dashboard');
else if (moduleId === 'goals') goToPage('goals', 'Opened goals module');
else if (moduleId === 'research') goToPage('research', 'Opened research module');
else showToast(`${mod.name} is a backend or future module`);
updateYucaStatus('Module focus', `${mod.name} is now the active surface`);
rememberEvent('module', `${mod.name} focused`);
updateChannelInbox();
}
function toggleModule(moduleId) {
const mod = yucaState.modules.find(item => item.id === moduleId);
if (!mod) return;
mod.enabled = !mod.enabled;
updateChannelInbox();
updateMemoryPreview();
rememberEvent('module', `${mod.name} ${mod.enabled ? 'enabled' : 'disabled'}`);
showToast(`${mod.name} ${mod.enabled ? 'enabled' : 'disabled'}`);
}
function startPairing(channelId) {
const channel = yucaState.channels.find(ch => ch.id === channelId);
if (!channel) return;
const code = makePairingCode();
channel.pendingCode = code;
channel.status = 'challenge-issued';
updateChannelInbox();
updateYucaStatus('Pairing policy', `${channel.name} challenge issued`);
updateBridgePreview(`Integration linking should be deliberate: new external routes should be linked carefully, while powerful actions remain gated by permissions and workflow approvals.`);
rememberEvent('pairing', `${channel.name} issued pairing code ${code}`);
addChatMessage('ai', `${escapeHtml(channel.name)} pairing challenge created.
Use code ${escapeHtml(code)} to approve this channel route. Until then, Yuca treats it as untrusted input and keeps higher-power actions locked.`);
}
function approvePairing(channelId) {
const channel = yucaState.channels.find(ch => ch.id === channelId);
if (!channel || !channel.pendingCode) return;
const approvedCode = channel.pendingCode;
channel.pendingCode = '';
channel.paired = true;
channel.status = 'ready';
yucaState.activeChannel = channel.id;
updateChannelInbox();
updateRoutePreview();
rememberEvent('pairing', `${channel.name} approved with ${approvedCode}`);
showToast(`${channel.name} paired`);
}
function queueChannelAction(channelId) {
const channel = yucaState.channels.find(ch => ch.id === channelId);
if (!channel) return;
queueApproval(`${channel.name} outbound summary`, `Send a concise portfolio update through ${channel.name} after approval.`, () => {
updateBridgePreview(`Gateway would deliver the approved output through ${channel.name} while keeping the same session route and transcript trail.`);
addChatMessage('ai', `${escapeHtml(channel.name)} action approved.
Yuca would now package the response, send it through the runtime, and append the delivery to the transcript + run log stream.`);
});
updateYucaStatus('Control request', `${channel.name} outbound action queued`);
}
function setCurrentPage(page) {
yucaState.currentPage = page;
updateMemoryPreview();
updateRoutePreview();
renderCanvasPreview();
}
const originalNavigateTo = navigateTo;
navigateTo = function(page) {
originalNavigateTo(page);
setCurrentPage(page);
if (page !== 'ai' && yucaState.companionAwake) maybeShowCompanionNudge();
};
function goToPage(page, toast) {
navigateTo(page);
if (toast) showToast(toast);
rememberEvent('navigation', `Moved to ${page}`);
}
function openCanvasScene(scene, detail) {
yucaState.canvas.open = true;
yucaState.canvas.scene = scene;
yucaState.canvas.detail = detail;
renderCanvasPreview();
rememberEvent('canvas', `${scene} scene prepared`);
}
function detectIntent(q) {
const query = q.toLowerCase();
if (/(architecture|system|platform|nocobase|data model|module registry|business app)/.test(query)) return 'architecture';
if (/(profile|profiles|workspace model|model wrapper|preset)/.test(query)) return 'profiles';
if (/(knowledge|vault|rag|retrieval|file context|hybrid search)/.test(query)) return 'knowledge';
if (/(note|notes|notebook|saved note|sticky)/.test(query)) return 'notes';
if (/(pipeline|pipelines|stage|stages|rail|filter chain)/.test(query)) return 'pipeline';
if (/(tool|tools|function|capabilities|permissions|approval|queue|role|access)/.test(query)) return 'tools';
if (/(workflow|automation|trigger|alert rule|milestone|queue gate|ops log)/.test(query)) return 'workflow';
if (/(risk|allocation|diversification|portfolio|exposure|largest holding|biggest risk)/.test(query)) return 'risk';
if (/(goal|emergency|retirement|house|fund|what if i add|which goal)/.test(query)) return 'goals';
if (/(research|ticker|stock|aapl|nvda|msft|googl|vti|spy|btc|gld|compare to s&p|sp500|s&p)/.test(query)) return 'research';
if (/(page|navigate|go to|open dashboard|show goals|show research|show ai|show dashboard|show workbench)/.test(query)) return 'navigation';
if (/(builder|usage mode|edit layout|block|schema|workbench|canvas|module|plugin|registry)/.test(query)) return 'builder';
if (/(data object|data objects|schema|record|collection|table|model|entity)/.test(query)) return 'data';
if (/(local runtime|runtime|ollama|model router|keep alive|keep_alive|runner|runner pool|warm model|local model|offline)/.test(query)) return 'runtime';
if (/(embed|embedding|semantic search|vector)/.test(query)) return 'embed';
if (/(blob|artifact|digest|cache|store)/.test(query)) return 'blob';
if (/(stream|ndjson|stream monitor|token stream|live response)/.test(query)) return 'stream';
if (/(local runtime|runtime|ollama|model router|keep alive|keep_alive|runner|runner pool|warm model|local model|offline)/.test(query)) return 'runtime';
if (/(embed|embedding|semantic search|vector)/.test(query)) return 'embed';
if (/(blob|artifact|digest|cache|store)/.test(query)) return 'blob';
if (/(stream|ndjson|stream monitor|token stream|live response)/.test(query)) return 'stream';
if (/(replay|snapshot|scenario|validate|validation|coverage|regression|freeze time|keploy|mock registry|replay lab)/.test(query)) return 'replay';
if (/(integration|http|api|webhook|request|sync)/.test(query)) return 'integration';
if (/(context|memory|session|ops|log|history|audit)/.test(query)) return 'context';
return 'general';
}
function renderArchitectureAnswer() {
updateBridgePreview(`Gold Days can take the best NocoBase lessons without becoming a full no-code platform: data objects first, modules second, workflows third, then role-aware actions and ops logging on top.`);
return `Yuca can be framed like a modular business app workbench inside your site.
1. Data objects: users, accounts, positions, goals, alerts, AI sessions, research items. 2. Builder mode: switch between normal usage and layout/action editing. 3. Modules: Portfolio, Goals, Research, Alerts, Yuca AI, Admin Builder. 4. Workflows: visible triggers, actions, statuses, and role gates. 5. Integrations: route external requests through one bridge layer. 6. Permissions: tools unlock by role instead of being globally open. 7. Workbench: action-based dashboard blocks instead of static sections. 8. Ops: logs, last runs, failed syncs, and alert history stay visible.
`;
}
function aiResponseFor(q) {
const intent = detectIntent(q);
yucaState.lastIntent = intent;
yucaState.companionAwake = true;
yucaState.memory.push({ q, intent, time: Date.now() });
if (yucaState.memory.length > 8) yucaState.memory.shift();
updateMemoryPreview();
updateRoutePreview();
rememberEvent('user_intent', `${intent} → ${q.slice(0, 54)}${q.length > 54 ? '…' : ''}`);
if (intent === 'profiles') {
updateYucaStatus('Workspace profiles', `${yucaState.workspaceProfiles.length} profile wrappers ready`);
return `Yuca uses named workspace profiles, not just raw models.
${yucaState.workspaceProfiles.map(p => `${escapeHtml(p.name)} → base ${escapeHtml(p.base)} with knowledge ${p.knowledge.map(escapeHtml).join(', ')} and tools ${p.tools.map(escapeHtml).join(', ')}`).join('
')}
This keeps the Claude-style coordinator intact while giving each surface a cleaner operating profile.`;
}
if (intent === 'knowledge') {
updateYucaStatus('Knowledge vault', `${yucaState.knowledgeVault.length} collections mounted`);
return `Yuca now has a Knowledge Vault layer.
${yucaState.knowledgeVault.map(v => `${escapeHtml(v.name)} uses ${escapeHtml(v.mode)} retrieval with ${escapeHtml(String(v.files))} files for ${escapeHtml(v.access)} access.`).join(' ')}
This sits under the Claude-style memory system: session memory remembers the user, while the vault provides reusable reference context.`;
}
if (intent === 'notes') {
updateYucaStatus('Workspace notes', `${yucaState.workspaceNotes.length} saved notes available`);
return `Yuca Notes are persistent context objects outside any single chat.
That gives you reusable planning and research context without polluting durable memory.`;
}
if (intent === 'pipeline') {
updateYucaStatus('Pipeline rail', `${yucaState.pipelineRail.length} stages visible`);
return `Yuca now exposes a pipeline rail behind the bridge.
This improves structure without replacing the Claude-style coordinator, approvals, or companion.`;
}
if (intent === 'tools') {
updateYucaStatus('Tools + functions', 'Workspace split is active');
return `Yuca separates tools from functions.
Tools are model-facing capabilities exposed through the bridge and permissions. Functions are platform behaviors controlled from the admin side. That keeps the Claude-style approval path clean and avoids turning Yuca into an unsafe plugin free-for-all.`;
}
if (intent === 'replay') {
updateYucaStatus('Replay lab', `${yucaState.snapshotVault.length} snapshots and ${yucaState.scenarioRegistry.length} scenarios ready`);
updateBridgePreview(`Keploy-style replay belongs under Yuca's ops layer: capture trusted snapshots, replay production-like flows, freeze time when needed, validate diffs, then block activation on regressions without disturbing the Claude-style memory, bridge, approvals, heartbeat, or companion.`);
renderReplayLab();
renderScenarioRegistry();
renderValidationPanels();
return `Yuca now has a replay-and-validation layer, not just chat memory and automation.
Replay Lab replays trusted snapshots like ${escapeHtml(yucaState.replayLab.activeSnapshot)} with ${yucaState.replayLab.freezeTime ? 'freeze-time enabled' : 'live time'} so weekly digests, approval flows, and milestone nudges can be validated deterministically.
Scenario Registry tracks realistic journeys like ${escapeHtml(yucaState.scenarioRegistry[1].name)} and ${escapeHtml(yucaState.scenarioRegistry[2].name)} so Yuca can prove that profiles, connectors, approvals, and workflows still behave correctly.
Coverage + validation gives you a product-facing answer to “did we break anything?” without replacing the Claude-style coordinator or the Beads-style ledger.`;
}
if (intent === 'risk') {
const top = getLargestHolding();
const total = getTotalPortfolioValue();
const funds = holdings.filter(h => h.symbol !== top.symbol).map(h => h.symbol).join(', ');
updateYucaStatus('Portfolio object', `Largest holding ${top.symbol} at ${top.alloc}%`);
updateBridgePreview(`This answer should come from the positions + accounts objects, not from hardcoded chat copy. That same object layer can feed dashboard cards, AI, exports, and workflows.`);
return `Your biggest concentration risk is ${top.symbol} at ${top.alloc}%.
Total tracked portfolio value is ${formatMoney(total)}. A data-model-first build would let Yuca send this same result to the dashboard, an alert workflow, and a saved ops record without recalculating it three different ways.
Cleaner move: push new money toward broader exposure like ${funds}.`;
}
if (intent === 'goals') {
const extra = /200/.test(q) ? 200 : 0;
const proj = getGoalProjection(extra);
updateYucaStatus('Goal object', `${proj.goal.title} projection refreshed`);
updateBridgePreview(`Goals should live as records with actions attached: update target, recalc projection, trigger milestone workflow, and log the event.`);
return `${proj.goal.title} is ${Math.round((proj.goal.current / proj.goal.target) * 100)}% complete.
You still need ${formatMoney(proj.gap)}. At ${formatMoney(proj.monthly)} per month, Yuca estimates about ${proj.months} months remaining.${extra ? ` That includes the extra ${formatMoney(extra)} monthly boost.` : ''}
Why this matters: the same goal record can power cards, AI coaching, workflows, and future partner/admin views.`;
}
if (intent === 'runtime') {
updateYucaStatus('Local runtime', `${yucaState.runtime.chatModel} via ${yucaState.runtime.host}`);
updateBridgePreview(`An Ollama-style runtime layer helps Gold Days feel private and resilient: local-first inference, model-specific routing, keep_alive runner control, and visible endpoints for chat, generate, embed, process state, create, show, and blob-backed assets.`);
renderRuntimePanel();
renderRuntimeSessions();
return `Yuca can grow a real local runtime layer instead of being only a front-end chat shell.
Practical site use: portfolio chat stays local when possible, research embeddings feed semantic search, image uploads go through the vision route, and the builder sees the runtime state instead of guessing.`;
}
if (intent === 'embed') {
updateYucaStatus('Embedding route', `${yucaState.runtime.embedModel} queued for semantic tasks`);
updateBridgePreview(`Embeddings should become a first-class service in Gold Days: watchlists, research similarity, policy retrieval, and ops lookup all benefit from a dedicated embed route instead of piggybacking on chat.`);
return `Use a dedicated embedding route, not the chat model, for retrieval work.
Gold Days can embed research notes, help docs, alerts, and prior AI sessions so Yuca can semantically retrieve the right context. That makes the site feel faster, cheaper, and more accurate than re-reading everything through the main chat model.`;
}
if (intent === 'blob') {
updateYucaStatus('Artifact store', `${yucaState.blobs.length} blob-backed assets available`);
updateBridgePreview(`Blob-style storage makes AI outputs reusable. Instead of treating every summary as disposable text, Gold Days can keep digest-addressed artifacts for reports, cards, previews, caches, and multimodal payloads.`);
renderBlobPanel();
return `The blob store is one of the best patterns to copy.
AI outputs should become reusable assets: cached reports, generated cards, vector caches, prompt render previews, and image attachments. That gives Yuca a cleaner memory and makes downloads, previews, and rerenders much easier.`;
}
if (intent === 'stream') {
updateYucaStatus('Streaming path', 'NDJSON-style responses active');
updateBridgePreview(`Streaming should be visible in the product. Gold Days can show first-token time, live stream state, completion stats, and runner state so the AI feels alive and explainable instead of like a black box.`);
return `Yuca should expose a real stream monitor.
Show first-token time, stream/live mode, tokens-per-second, runner status, and whether the answer came from local chat, embeddings, or a heavier multimodal path. That is more useful than a generic typing indicator.`;
}
if (intent === 'research') {
const match = q.toUpperCase().match(/(AAPL|NVDA|MSFT|GOOGL|VTI|SPY|BTC|GLD)/);
const symbol = match ? match[1] : yucaState.activeSymbol;
yucaState.activeSymbol = symbol;
document.getElementById('ticker-input').value = symbol;
researchStock();
goToPage('research', `${symbol} loaded in Research Lab`);
updateYucaStatus('Research module', `${symbol} research card generated`);
updateBridgePreview(`Research should be one module in the registry. The same data fetch can feed the page, Yuca summary, watchlist alerts, and an ops log entry.`);
return `${symbol} research is now open in the Research Lab.
NocoBase’s strongest lesson here is modularity: research should be its own module with its own records, actions, and permissions instead of just being a random branch in chat logic.`;
}
if (intent === 'navigation') {
if (/goal/.test(q.toLowerCase())) goToPage('goals', 'Opened Wealth Goals');
else if (/research/.test(q.toLowerCase())) goToPage('research', 'Opened Research Lab');
else if (/ai|workbench/.test(q.toLowerCase())) goToPage('ai', 'Returned to Yuca Workspace Control');
else goToPage('landing', 'Returned to dashboard');
updateYucaStatus('Workbench nav', 'Page transition executed');
return `Navigation executed.
In a stronger app build, page changes should still preserve object context, workflow status, and the ops trail so Yuca always knows what surface it is standing on.`;
}
if (intent === 'tools') {
updateYucaStatus('Role map', 'Explaining tools and access rules');
updateBridgePreview(`Role-aware tools are one of the highest-value upgrades. Guest, member, premium, operator, and admin should not all get the same actions.`);
return `Yuca should expose role-aware tools, not one flat tool list.
`;
}
if (intent === 'workflow') {
updateYucaStatus('Workflow automation', 'Explaining visible triggers and actions');
renderApprovalQueue();
updateBridgePreview(`Workflow automation should be visible: trigger, action, status, and gate. That is cleaner than burying these rules inside random buttons or hidden API calls.`);
return `Workflow automation should be a first-class part of the site.
This is the part of NocoBase that maps best to Yuca: triggers, actions, and logs should be visible enough that the user understands what the system is doing.`;
}
if (intent === 'builder') {
toggleBuilderMode(true);
openCanvasScene('builder-mode', 'Layout editing, module toggles, workflow blocks, and integration actions are now framed as editable builder surfaces.');
updateYucaStatus('Builder mode', 'Layout and module editing explained');
updateBridgePreview(`Usage mode is the live product. Builder mode is the editing layer. That split lets you rearrange blocks, actions, and modules without rewriting the whole page every time.`);
return `Builder mode is where this site gets much stronger.
Usage mode: normal member experience. Builder mode: rearrange blocks, toggle modules, edit actions, tune workflows, and review integrations. Best result: you can evolve Gold Days like an app, not just a giant HTML file.
`;
}
if (intent === 'data') {
updateYucaStatus('Data model', 'Explaining shared objects');
updateBridgePreview(`The app should anchor itself around shared objects: ${yucaState.dataObjects.join(', ')}. Once those are stable, both the UI and Yuca become easier to grow.`);
return `Your core data objects should be:
Why this matters: every object can have blocks, actions, permissions, and workflows attached to it, which is much cleaner than duplicating logic page by page.`;
}
if (intent === 'integration') {
updateYucaStatus('Integration bridge', 'Explaining outbound actions');
updateBridgePreview(`Market data, AI calls, notifications, exports, and webhooks should move through one integration bridge layer. The frontend should ask for named actions, not hand-roll scattered requests.`);
return `Integrations should be treated like named actions.
Examples: fetch market data, request AI summary, send milestone alert, export a report, fire a webhook. Cleaner architecture: one bridge layer manages auth, logging, retries, and permissions.`;
}
if (intent === 'context') {
updateYucaStatus('Ops + context', 'Explaining memory, logging, and history');
updateBridgePreview(`Yuca should keep just enough context to stay useful, but the real trust builder is the ops layer: event history, failed requests, reruns, and last changed objects.`);
return `Yuca should remember context, but it should also explain itself.
Keep event history for object changes, workflow runs, failed syncs, alert sends, and AI sessions. That turns the app from “black box” into “inspectable system.”`;
}
if (intent === 'architecture') {
updateYucaStatus('Architecture mode', 'Explaining system layers');
return renderArchitectureAnswer();
}
updateYucaStatus('General coach', 'Providing summary guidance');
updateBridgePreview(`The NocoBase lesson is simple: Gold Days should feel like a modular business app with data objects, builder mode, workflows, permissions, and logs underneath the visuals.`);
return `Yuca is now framed around the strongest NocoBase-style ideas that fit your site.
The most useful pieces are data objects behind the UI, a real builder mode, module registry, visible workflows, role-aware tools, integration actions, and an ops panel that explains what happened.`;
}
function sendAIMessage() {
const input = document.getElementById('ai-input');
const q = input.value.trim();
if (!q) return;
addChatMessage('user', escapeHtml(q));
input.value = '';
updateYucaStatus('Routing', 'Analyzing intent, route, and channel scope');
setTimeout(() => addChatMessage('ai', aiResponseFor(q)), 380);
}
function quickReply(btn) {
document.getElementById('ai-input').value = btn.textContent;
sendAIMessage();
}
function maybeShowCompanionNudge(forceText) {
const note = document.getElementById('yuca-companion-note');
if (!note) return;
let message = forceText || '';
if (!message && yucaState.currentPage === 'landing' && yucaState.memory.some(item => item.intent === 'risk')) {
message = 'Yuca remembers a concentration-risk thread and can push it into a workflow or alert object.';
} else if (!message && yucaState.currentPage === 'goals') {
message = 'Yuca can keep this goal thread pinned even if you jump between usage mode and builder mode.';
} else if (!message && yucaState.currentPage === 'research') {
message = `Yuca can carry ${yucaState.activeSymbol} research into the workbench without resetting the route.`;
}
if (!message) {
note.style.display = 'none';
return;
}
note.textContent = message;
note.style.display = 'block';
setTimeout(() => {
if (note.textContent === message) note.style.display = 'none';
}, 5200);
}
function openYucaCompanion() {
yucaState.companionAwake = true;
navigateTo('ai');
showToast('Yuca companion opened');
maybeShowCompanionNudge('Workbench session resumed inside Yuca AI.');
updateMemoryPreview();
rememberEvent('companion', 'Companion opened from floating orb');
}
function approveFirstRequest() {
if (!yucaState.approvals.length) {
showToast('No pending requests');
return;
}
approveRequest(yucaState.approvals[0].id);
}
function seedChat() {
const el = document.getElementById('chat-messages');
if (el.children.length) return;
yucaState.lastIntent = 'welcome';
yucaState.companionAwake = true;
updateYucaStatus('Welcome', 'Data objects, builder mode, workflows, and ops surfaces initialized');
addChatMessage('ai', `Welcome. I'm Yuca AI.
This build now keeps the module and workflow gains from v7, then adds an Ollama-style runtime layer: local model routing, keep_alive controls, embeddings, blob-backed artifacts, and a visible stream + runner surface.
`);
updateMemoryPreview();
updateRoutePreview();
updateChannelInbox();
renderApprovalQueue();
renderSessionTimeline();
renderTranscriptStream();
renderCanvasPreview();
rememberEvent('session', 'Yuca shell initialized');
renderRuntimePanel();
renderBlobPanel();
renderRuntimeSessions();
renderWorkspaceProfiles();
renderKnowledgeVault();
renderWorkspaceNotes();
renderPipelineRail();
renderFunctionsPanel();
renderExecutionCenter();
renderSubflowLibrary();
renderConnectorRegistry();
renderRunHistory();
maybeShowCompanionNudge('Yuca is active. Tap the orb anytime to jump back into the active workbench route.');
}
/* ── ACTIONS ────────────────────────────── */
function addHolding() {
holdings.unshift({ symbol: 'NVDA', name: 'NVIDIA Corp', shares: 21, avgCost: 112, current: 138, value: 2898, gain: 546, alloc: 2 });
renderHoldings();
showToast('NVDA added to portfolio');
}
function toggleDemoMode() { showToast('Demo portfolio refreshed'); renderHoldings(); renderGoals(); }
function showProfile() { showToast('Profile & settings panel — coming soon'); }
function fakeExport() { showToast('Monthly Gold Report export triggered'); }
/* ── HERO BG ANIMATE ─────────────────────── */
function animateHeroBg() {
setTimeout(() => document.getElementById('hero-bg').classList.add('loaded'), 100);
}
/* ── INIT ───────────────────────────────── */
window.onload = function() {
buildTicker();
renderHoldings();
renderGoals();
initDashboard();
navigateTo('landing');
animateHeroBg();
};