fix(aiui): fabricated demo content is demo-build-only, fake credential scrubbed (S7)

Per operator decision 2026-08-07 (mock content is isolated to
demo.archipelago-foundation.org, never in shipped code): the auto-seeded
'node-demo' conversation (invented balances, file listings, bitcoin.conf)
no longer ships on nodes — a VITE_DEMO_CONTENT build flag (or dev) gates
it, /seed, and the Guide 'Load Demo' button. The genuine onboarding guide
still seeds everywhere. The fixture's bitcoin.conf rpcpassword is now
unmistakably example-shaped: fake must never look like a real credential.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-07 06:28:51 -04:00
co-authored by Claude
parent 4e6df488fc
commit 9bb38c36d3
6 changed files with 50 additions and 11 deletions
@@ -113,7 +113,7 @@ txindex=1
# RPC Settings
rpcuser=archipelago
rpcpassword=archipelago123
rpcpassword=EXAMPLE-ONLY-not-a-real-password
rpcallowip=127.0.0.1
rpcbind=127.0.0.1
rpcport=8332
@@ -148,6 +148,7 @@
import { computed, ref, watch, nextTick } from 'vue'
import { useVirtualizer } from '@tanstack/vue-virtual'
import { useChatStore } from '@/stores/chat'
import { DEMO_CONTENT_ENABLED } from '@/utils/demoContent'
import { useAI, streamWithModel } from '@/composables/useAI'
import { useContentPanel } from '@/composables/useContentPanel'
import { useComparisonMode } from '@/composables/useComparisonMode'
@@ -354,6 +355,18 @@ async function handleSend(text: string, images: ImageAttachment[] = []) {
}
if (trimmed === '/seed') {
// Demo-site/dev only (S7): the seed showcase is fabricated example
// content and must not ship in the node build.
if (!DEMO_CONTENT_ENABLED) {
const convId = chatStore.activeConversationId
if (convId) {
chatStore.addMessage(convId, {
role: 'assistant',
content: 'The `/seed` showcase is only available in demo builds.',
})
}
return
}
await chatStore.loadSeedChats()
// loadSeedChats already switches to the seed conversation
// Collapse chat so user sees PromptIndex for quick picking
@@ -86,6 +86,7 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { usePromptTemplateStore, extractVariables, type PromptTemplate } from '@/stores/promptTemplates'
import { DEMO_CONTENT_ENABLED } from '@/utils/demoContent'
interface PaletteCommand {
id: string
@@ -99,7 +100,10 @@ const BUILT_IN_COMMANDS: PaletteCommand[] = [
{ id: 'cmd-nostr', slash: '/nostr', title: 'Nostr', preview: 'Browse the Nostr network feed' },
{ id: 'cmd-design', slash: '/design', title: 'Design System', preview: 'Open the design system viewer' },
{ id: 'cmd-search', slash: '/search ', title: 'Search', preview: 'Search your content library' },
{ id: 'cmd-seed', slash: '/seed', title: 'Seed', preview: 'Load seed conversations for all content types' },
// /seed loads fabricated showcase content — demo-site/dev builds only (S7)
...(DEMO_CONTENT_ENABLED
? [{ id: 'cmd-seed', slash: '/seed', title: 'Seed', preview: 'Load seed conversations for all content types' }]
: []),
{ id: 'cmd-freefilms', slash: '/freefilms', title: 'Free Films', preview: 'Browse free documentary films from InDeeHub' },
]
+4 -2
View File
@@ -197,8 +197,9 @@
</ul>
</section>
<!-- Try It -->
<section class="guide-section">
<!-- Try It — demo builds only (S7): the demo conversation is
fabricated example node state, demo-site/dev only -->
<section v-if="DEMO_CONTENT_ENABLED" class="guide-section">
<h2 class="section-title">
<span class="section-icon">&#x1F680;</span>
Try It Out
@@ -220,6 +221,7 @@
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useChatStore } from '@/stores/chat'
import { DEMO_CONTENT_ENABLED } from '@/utils/demoContent'
const router = useRouter()
const chatStore = useChatStore()
+16 -7
View File
@@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'
import type { Message, Conversation, WebSearchResult } from '@aiui/core/types/message'
import { apiFetch } from '@/utils/api-fetch'
import { DEMO_CONTENT_ENABLED } from '@/utils/demoContent'
function generateId(): string {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
@@ -406,8 +407,9 @@ export const useChatStore = defineStore('chat', () => {
return siblings
}
/** Load all seed prompts as a single conversation */
/** Load all seed prompts as a single conversation — demo builds only (S7) */
async function loadSeedChats(): Promise<number> {
if (!DEMO_CONTENT_ENABLED) return 0
try {
const { seedPromptsToConversation } = await import('@/__tests__/fixtures/seedPrompts')
const conv = seedPromptsToConversation() as Conversation
@@ -429,8 +431,10 @@ export const useChatStore = defineStore('chat', () => {
}
}
/** Load node demo conversation showing local node capabilities */
/** Load node demo conversation — demo builds only (S7); the content is
* fabricated node state and must not ship on real nodes */
async function loadNodeDemoChat(): Promise<number> {
if (!DEMO_CONTENT_ENABLED) return 0
try {
const { nodeDemoToConversation } = await import('@/__tests__/fixtures/nodeDemoPrompts')
const conv = nodeDemoToConversation() as Conversation
@@ -472,11 +476,16 @@ export const useChatStore = defineStore('chat', () => {
}
}
/** Seed demo conversations (guide + node demo) on first use */
/** Seed demo conversations (guide always; node demo only in demo builds) */
async function seedDemoConversations(): Promise<void> {
// The guide is genuine onboarding documentation — always seed it.
// `node-demo` is FABRICATED node state (invented balances, file listings,
// a fake bitcoin.conf) — demo-site/dev builds only (S7, operator
// decision 2026-08-07: mocks belong to the demo site, not shipped code).
const wantDemo = DEMO_CONTENT_ENABLED
const hasGuide = conversations.value.has('aiui-guide')
const hasDemo = conversations.value.has('node-demo')
if (hasGuide && hasDemo) {
if (hasGuide && (hasDemo || !wantDemo)) {
// Already seeded — still select guide if nothing active
if (!activeConversationId.value) {
activeConversationId.value = 'aiui-guide'
@@ -485,15 +494,15 @@ export const useChatStore = defineStore('chat', () => {
}
try {
const { guideToConversation } = await import('@/__tests__/fixtures/guideConversation')
const { nodeDemoToConversation } = await import('@/__tests__/fixtures/nodeDemoPrompts')
const guide = guideToConversation() as Conversation
const demo = nodeDemoToConversation() as Conversation
const merged = new Map(conversations.value)
if (!merged.has(guide.id)) {
merged.set(guide.id, guide)
immediateIDBSave(guide)
}
if (!merged.has(demo.id)) {
if (wantDemo && !merged.has('node-demo')) {
const { nodeDemoToConversation } = await import('@/__tests__/fixtures/nodeDemoPrompts')
const demo = nodeDemoToConversation() as Conversation
merged.set(demo.id, demo)
immediateIDBSave(demo)
}
@@ -0,0 +1,11 @@
/**
* Demo-content gate (operator decision 2026-08-07): seed/mock showcase
* conversations exist for the demo.archipelago-foundation.org website ONLY
* and must not ship in the node build. Demo builds set
* `VITE_DEMO_CONTENT=true`; local dev keeps them for development.
*
* Anything behind this flag is fabricated example content — never present
* it as real node data in a shipped build.
*/
export const DEMO_CONTENT_ENABLED =
import.meta.env.DEV || import.meta.env.VITE_DEMO_CONTENT === 'true'