orb-daemon 0.1.0

Orb DevKit desktop daemon - secure TCP/WS bridge for the Orb mobile app
<template>
  <!-- Floating status pill — shown on env, vibecode, passwords pages -->
  <Teleport to="body">
    <Transition name="daemon-pill">
      <div v-if="visible"
        class="fixed z-[500] flex items-center gap-2 px-3.5 py-2 rounded-2xl"
        :style="pillStyle"
        style="bottom:calc(90px + env(safe-area-inset-bottom));right:16px;backdrop-filter:blur(20px);">

        <!-- Status dot -->
        <div class="w-2 h-2 rounded-full flex-shrink-0"
          :class="connected ? 'animate-pulse' : ''"
          :style="{ background: connected ? '#34d399' : '#ef4444' }">
        </div>

        <!-- Label -->
        <span class="text-[11px] font-mono font-bold"
          :style="{ color: connected ? '#34d399' : '#f87171' }">
          {{ connected ? `daemon · ${latency != null ? latency + 'ms' : 'live'}` : 'daemon · offline' }}
        </span>

        <!-- Sync button (when connected) -->
        <button v-if="connected && showSync"
          @click="doSync"
          :disabled="syncing"
          class="flex items-center gap-1 px-2.5 py-1 rounded-xl text-[10px] font-mono font-black active:scale-90 transition-all"
          :style="{ background: accent + '20', border: `1px solid ${accent}33`, color: accent }"
          :class="syncing ? 'opacity-60' : ''">
          <ArrowUpDown :size="11" :stroke-width="2.5" :class="syncing ? 'animate-spin' : ''" />
          {{ syncing ? 'syncing' : 'sync' }}
        </button>

        <!-- Pair button (when disconnected) -->
        <button v-else-if="!connected"
          @click="$emit('open-pair')"
          class="flex items-center gap-1 px-2.5 py-1 rounded-xl text-[10px] font-mono font-black active:scale-90"
          style="background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.2);color:#f87171;">
          <Link :size="11" :stroke-width="2.5" />
          pair
        </button>
      </div>
    </Transition>
  </Teleport>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import { ArrowUpDown, Link } from 'lucide-vue-next'
import { settings } from '../composables/useStore'
import { useDaemon } from '../composables/useDaemon'

const props = defineProps<{
  visible?: boolean
  showSync?: boolean
  onSync?: () => Promise<void>
}>()

const emit = defineEmits<{
  'open-pair': []
  'sync-done': []
}>()

const { connected, latency } = useDaemon()
const accent = computed(() => settings.value.accentColor)
const syncing = ref(false)

const pillStyle = computed(() => ({
  background: connected.value
    ? 'rgba(6, 22, 14, 0.85)'
    : 'rgba(22, 6, 6, 0.85)',
  border: connected.value
    ? '1px solid rgba(52, 211, 153, 0.2)'
    : '1px solid rgba(239, 68, 68, 0.2)',
}))

async function doSync() {
  if (!props.onSync || syncing.value) return
  syncing.value = true
  try {
    await props.onSync()
    emit('sync-done')
  } finally {
    syncing.value = false
  }
}
</script>

<style scoped>
.daemon-pill-enter-active { transition: all 0.3s cubic-bezier(0.34, 1.1, 0.64, 1); }
.daemon-pill-leave-active { transition: all 0.2s ease; }
.daemon-pill-enter-from   { opacity: 0; transform: translateY(12px) scale(0.9); }
.daemon-pill-leave-to     { opacity: 0; transform: translateY(8px) scale(0.95); }
</style>