Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/USX_SURFACE_BASELINE_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,14 @@ Form and action controls should use:

- Use only var(--usx-color-*) in component CSS.
- Do not use --pico-* in new component-level styles.
- Use UIcon with Material Symbols; do not use emoji as UI icons.
- Use UIcon with Material Symbols; do not use emoji as UI icons or action buttons.
- Amber CRT phosphor (`text-amber-*`, raw phosphor glow) is strictly forbidden. Semantic warnings must use `var(--usx-color-warning)`.

## Surface Toolbar & Button Standards

- **No Redundant Secondary Back Bars**: Surfaces hosted within `AppShell` must NOT render redundant `← Dashboard` or `← Back` top bars. The global shell (`GlobalToolbar`) already provides permanent one-click home and finder navigation. Surfaces begin with their canonical header (`[prefix]-header`) featuring title, status badges (`UBadge`), and action controls.
- **Button Styling Consistency**: Action buttons must strictly use `.usx-btn` (with modifiers `.usx-btn--primary`, `.usx-btn--secondary`, `.usx-btn--danger`, `.usx-btn--sm`) or `UButton`. Do not invent ad-hoc button classes or use misspelt prefixes (e.g. `.uxs-btn`).
- **Sovereign Sync Integration**: System surfaces expose calm, serene indicators for host sync status (Apple PIM JXA, BitChat LAN mesh, sovereign `~/Vault` storage) without visual clutter.

## Compliance Rubric

Expand Down
51 changes: 51 additions & 0 deletions frontend-vue/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,57 @@ export const ucoreApi = {
body: JSON.stringify(source ? { source } : {}),
}),
},
host: {
syncStatus: () => request(`${UCORE_API}/api/host/sync/status`),
mailIntake: (body?: { limit?: number; unread_only?: boolean }) =>
request(`${UCORE_API}/api/host/mail/intake`, {
method: "POST",
body: JSON.stringify(body || {}),
}),
imessageIntake: (body?: { limit?: number }) =>
request(`${UCORE_API}/api/host/imessage/intake`, {
method: "POST",
body: JSON.stringify(body || {}),
}),
mailArchive: (messageId: string) =>
request(`${UCORE_API}/api/host/mail/archive`, {
method: "POST",
body: JSON.stringify({ message_id: messageId }),
}),
mailFlag: (messageId: string, flagIndex: number = 0) =>
request(`${UCORE_API}/api/host/mail/flag`, {
method: "POST",
body: JSON.stringify({ message_id: messageId, flag_index: flagIndex }),
}),
},
workflow: {
actionToTask: (body: {
text: string;
source?: string;
source_ref?: string;
priority?: string;
sender?: string;
}) =>
request(`${UCORE_API}/api/workflow/tasks/action-to-task`, {
method: "POST",
body: JSON.stringify(body),
}),
approveTask: (id: string) =>
request(
`${UCORE_API}/api/workflow/tasks/${encodeURIComponent(id)}/approve`,
{
method: "POST",
},
Comment on lines +215 to +220
),
rejectTask: (id: string, reason?: string) =>
request(
`${UCORE_API}/api/workflow/tasks/${encodeURIComponent(id)}/reject`,
{
method: "POST",
body: JSON.stringify({ reason }),
},
),
},
};

/**
Expand Down
25 changes: 25 additions & 0 deletions frontend-vue/src/skills/organisms/GlobalToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@

<!-- Right: explicit global mode + display controls -->
<div class="global-toolbar__right">
<button
class="global-toolbar__icon-only global-toolbar__sync-btn"
title="Host Ecosystem Sync Active (Dashboard)"
@click="navigate('/')"
>
<span class="global-toolbar__sync-dot" />
Comment on lines +87 to +90
<UIcon name="sync" />
</button>
<button
class="global-toolbar__icon-only global-toolbar__theme-toggle"
:title="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
Expand Down Expand Up @@ -145,3 +153,20 @@ function navigate(path: string) {
router.push(path);
}
</script>

<style scoped>
.global-toolbar__sync-btn {
position: relative;
}

.global-toolbar__sync-dot {
position: absolute;
top: 8px;
right: 8px;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--usx-color-success);
pointer-events: none;
}
</style>
74 changes: 32 additions & 42 deletions frontend-vue/src/surfaces/bitchat/BitChatSurface.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
<!-- Top Toolbar -->
<header class="bitchat-toolbar">
<div class="bitchat-toolbar__left">
<button class="bitchat-btn-back" @click="router.push('/')" title="Back to Dashboard">
← Dashboard
</button>
<span class="bitchat-title">BitChat</span>
<span class="bitchat-badge font-mono">Decentralized LAN Mesh</span>
<h1 class="bitchat-title">BitChat</h1>
<UBadge type="success" size="sm">Decentralized LAN Mesh</UBadge>
</div>

<div class="bitchat-toolbar__center">
Expand All @@ -23,11 +20,13 @@
</div>

<div class="bitchat-toolbar__right">
<button class="bitchat-btn bitchat-btn--accent" @click="triggerBeacon" :disabled="isBroadcasting">
📡 {{ isBroadcasting ? 'Beaconing...' : 'Announce Beacon' }}
<button class="usx-btn usx-btn--sm usx-btn--primary bitchat-btn bitchat-btn--accent" @click="triggerBeacon" :disabled="isBroadcasting">
<UIcon name="cell_tower" />
<span>{{ isBroadcasting ? 'Beaconing...' : 'Announce Beacon' }}</span>
Comment on lines +23 to +25
</button>
<button class="bitchat-btn" @click="refreshAll" title="Refresh peers and messages">
🔄 Refresh
<button class="usx-btn usx-btn--sm usx-btn--secondary bitchat-btn" @click="refreshAll" title="Refresh peers and messages">
<UIcon name="refresh" />
<span>Refresh</span>
</button>
</div>
</header>
Expand Down Expand Up @@ -114,14 +113,15 @@
</span>
<button
v-if="selectedMsgIds.length > 0"
class="bitchat-btn bitchat-btn--primary font-mono text-xs"
class="usx-btn usx-btn--sm usx-btn--primary bitchat-btn bitchat-btn--primary font-mono text-xs"
@click="openSaveToBinderModal"
>
📁 Save to Binder
<UIcon name="folder" />
<span>Save to Binder</span>
</button>
<button
v-if="selectedMsgIds.length > 0"
class="bitchat-btn bitchat-btn--ghost font-mono text-xs"
class="usx-btn usx-btn--sm usx-btn--secondary bitchat-btn bitchat-btn--ghost font-mono text-xs"
@click="clearSelection"
>
Clear
Expand All @@ -132,7 +132,9 @@
<!-- Message Feed -->
<div class="bitchat-feed" ref="feedEl">
<div v-if="messages.length === 0" class="bitchat-empty-feed">
<div class="bitchat-empty-icon">💬</div>
<div class="bitchat-empty-icon">
<UIcon name="forum" />
</div>
<div class="bitchat-empty-title">No messages in {{ currentChannel }} yet</div>
<div class="bitchat-empty-subtitle text-zinc-400 text-xs">
Broadcast a message across your local mesh network or save notes into your binders.
Expand Down Expand Up @@ -162,10 +164,10 @@
{{ formatTimestamp(msg.timestamp) }}
</span>
<span v-if="msg.saved_to_binder" class="bitchat-tag bitchat-tag--saved font-mono" title="Saved to Binder Evidence">
📁 EVIDENCE
<UIcon name="folder" /> EVIDENCE
</span>
<span v-if="msg.task_id" class="bitchat-tag bitchat-tag--task font-mono" title="Promoted to Sovereign Task">
⚡ {{ msg.task_id }}
<UIcon name="bolt" /> {{ msg.task_id }}
</span>
</div>

Expand All @@ -174,18 +176,18 @@
<!-- Message Quick Actions -->
<div class="bitchat-msg-actions">
<button
class="bitchat-msg-btn font-mono"
class="usx-btn usx-btn--sm usx-btn--secondary bitchat-msg-btn font-mono"
@click="openConvertTaskModal(msg)"
title="Promote to .tasker markdown task"
>
⚡ Convert to Task
<UIcon name="bolt" /> Convert to Task
</button>
<button
class="bitchat-msg-btn font-mono"
class="usx-btn usx-btn--sm usx-btn--secondary bitchat-msg-btn font-mono"
@click="quickSaveSingleToBinder(msg)"
title="Save single message to binder evidence"
>
📁 Save to Binder
<UIcon name="folder" /> Save to Binder
</button>
</div>
</div>
Expand Down Expand Up @@ -214,11 +216,12 @@
></textarea>

<button
class="bitchat-btn bitchat-btn--send font-mono"
class="usx-btn usx-btn--primary bitchat-btn bitchat-btn--send font-mono"
:disabled="!draftText.trim()"
@click="handleSendMessage"
>
Send ↵
<span>Send</span>
<UIcon name="keyboard_return" />
</button>
</div>
</footer>
Expand All @@ -230,7 +233,9 @@
<div class="bitchat-modal">
<div class="bitchat-modal-header">
<h3>Save Discussion Evidence to Binder</h3>
<button class="bitchat-modal-close" @click="showSaveBinderModal = false">×</button>
<button class="bitchat-modal-close" @click="showSaveBinderModal = false">
<UIcon name="close" />
</button>
</div>
<div class="bitchat-modal-body">
<p class="text-xs text-zinc-400 mb-3">
Expand Down Expand Up @@ -268,7 +273,9 @@
<div class="bitchat-modal">
<div class="bitchat-modal-header">
<h3>Convert Action Item to Task</h3>
<button class="bitchat-modal-close" @click="showConvertTaskModal = false">×</button>
<button class="bitchat-modal-close" @click="showConvertTaskModal = false">
<UIcon name="close" />
</button>
</div>
<div class="bitchat-modal-body" v-if="activeTaskMsg">
<div class="bitchat-task-preview mb-3 p-3 bg-zinc-900 border border-zinc-800 rounded">
Expand Down Expand Up @@ -322,9 +329,8 @@

<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
import UIcon from '../../skills/atoms/UIcon.vue'
import UBadge from '../../skills/atoms/UBadge.vue'

// Channels
const channels = [
Expand Down Expand Up @@ -660,22 +666,6 @@ onUnmounted(() => {
gap: 0.75rem;
}

.bitchat-btn-back {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.15);
color: var(--usx-color-text, #e2e4e9);
padding: 0.35rem 0.75rem;
border-radius: 4px;
font-size: 0.8rem;
cursor: pointer;
transition: all 0.15s ease;
}

.bitchat-btn-back:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.3);
}

.bitchat-title {
font-size: 1.15rem;
font-weight: 700;
Expand Down
88 changes: 88 additions & 0 deletions frontend-vue/src/surfaces/browserui/ApiBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,55 @@ export interface BananaAssetResult {
markdown_embed?: string
citation_token?: string
offline?: boolean
lattice?: {
width: number
height: number
active_points: number
total_points: number
fill_ratio: number
rows: string[]
}
bob?: {
width: number
height: number
planes: number
bytes_per_row: number
plane_size_bytes: number
total_bytes: number
data_hex: string
}
error?: string
}

export interface QuantizeLatticeResult {
ok: boolean
width: number
height: number
active_points: number
Comment on lines +202 to +206
total_points: number
fill_ratio: number
rows: string[]
error?: string
}

export interface QuantizeTeletextResult {
ok: boolean
columns: number
rows: number
mosaic_lines: string[]
teletext: string
error?: string
}

export interface QuantizeBobResult {
ok: boolean
width: number
height: number
planes: number
bytes_per_row: number
plane_size_bytes: number
total_bytes: number
data_hex: string
error?: string
}

Expand Down Expand Up @@ -377,6 +426,45 @@ export async function generateBananaAsset(
}
}

export async function quantizeVectorToLattice(
options: { svg?: string; file_path?: string; target_width?: number; target_height?: number; threshold?: number }
): Promise<QuantizeLatticeResult> {
const res = await fetch(`${BASE}/api/vector/quantize/lattice`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(options),
signal: AbortSignal.timeout(15000),
})
if (!res.ok) throw new Error(`Lattice quantization failed (HTTP ${res.status})`)
return res.json()
}

export async function quantizeVectorToTeletext(
options: { svg?: string; file_path?: string }
): Promise<QuantizeTeletextResult> {
const res = await fetch(`${BASE}/api/vector/quantize/teletext-g1`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(options),
signal: AbortSignal.timeout(15000),
})
if (!res.ok) throw new Error(`Teletext quantization failed (HTTP ${res.status})`)
return res.json()
}

export async function quantizeVectorToBob(
options: { svg?: string; file_path?: string; target_width?: number; target_height?: number; planes?: number }
): Promise<QuantizeBobResult> {
const res = await fetch(`${BASE}/api/vector/quantize/bob`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(options),
signal: AbortSignal.timeout(15000),
})
if (!res.ok) throw new Error(`BOB blitter export failed (HTTP ${res.status})`)
return res.json()
}

export async function syncGoogleDriveVault(vaultPath?: string): Promise<DriveVaultSyncResult> {
const res = await fetch(`${BASE}/api/google/drive/scan`, {
method: "POST",
Expand Down
Loading
Loading