Dev Mode

Monte seu perfil cee com código.

JSX, hooks e roteamento dentro de uma sandbox segura. Six components, three utilities, zero supply chain — só sua imaginação e o cursor piscando.

JSX • esbuild no navegador • iframe sandboxed

01

O que é o Dev Mode

O Dev Mode te deixa escrever seu perfil cee.bio em JSX. É pra quem quer que a bio pareça um app, não uma lista de links — heróis animados, sub-páginas com rotas, layouts customizados. Se não é você, o editor visual em /dashboard/customize cobre tudo o mais.

Seu código roda dentro de um iframe sandboxed, sem acesso same-origin, sem cookies e sem rede além dos assets que você incorpora. O runtime expõe uma stdlib mínima: seis componentes e três utilitários. É só isso — sem <Badges />, sem <Music />, sem globais inesperados.

A superfície

  • Componentes: Card, Avatar, Bio, SocialLinks, Background, Link
  • Utilitários: useUser(), useRoute(), navigate(to)
  • Imports: react, react-dom, react-dom/client, cee + qualquer arquivo relativo do seu projeto

Os visitantes não pagam pela sua tooling. O editor pré-compila seu código com esbuild-wasm; o perfil público entrega só o bundle final (≤ 200KB) — sem download de 12MB de wasm pros seus fãs. Um badge persistente "Custom por @user" é renderizado fora do iframe, no maior z-index possível. Tá ali pra que um atacante esperto não consiga renderizar uma chrome falsa do cee.bio pra phishing nos seus visitantes. Não dá pra esconder.

Limites duros

Projeto-fonte 250KB · bundle compilado 200KB · clip de erro de runtime 4000 chars. Se bater no limite, divide em múltiplos arquivos ou tira aquele blob de dados gigante inline.

02

Primeiros passos

  1. Liga o Dev Mode em /dashboard/customize/dev.
  2. Seu projeto é multi-arquivo. O entry é main.tsx; imports relativos funcionam, extensões .tsx / .ts / .jsx / .js são auto-resolvidas, mais folder/index.*.
  3. Ao salvar dispara um compile esbuild no navegador (debounce 250ms). Se o Dev Mode tá on e a compilação falha, o editor recusa salvar — você não consegue publicar código quebrado.
  4. Os visitantes públicos recebem o bundle pré-compilado. Eles nunca baixam o esbuild-wasm.

O menor programa válido:

main.tsx
import { Card, Avatar, Bio, SocialLinks } from "cee";

export default function Profile() {
  return (
    <Card>
      <Avatar />
      <Bio />
      <SocialLinks />
    </Card>
  );
}

03

Referência de componentes

Cada componente lê do contexto do usuário quando faz sentido — você só passa props pra sobrescrever. Tudo é tolerante: dados ausentes renderizam nada em vez de quebrar.

<Card />

Container genérico. Layout flex, padding/radius/background tematizáveis, presets opcionais de hover/animação. Não lê dados do usuário.

<Card> ⤴ wrapper · container puro

PropTypeDefaultDescription
childrenReactNodeAnything you want inside the card.
paddingnumber24Inner padding in px.
radiusnumber16Border radius in px. Overridden by `rounded`.
roundedboolean | 'sm' | 'md' | 'lg' | 'xl' | 'full''md'Convenience shortcut for radius. true=16, full=9999, sm/md/lg/xl=8/16/24/32.
backgroundstring'rgba(255,255,255,0.04)'CSS background. Overridden by `gradient`.
gradient[string,string] | { from, to, angle? }Linear-gradient background.
borderstring'1px solid rgba(255,255,255,0.08)'CSS border shorthand.
shadowboolean | 'sm' | 'md' | 'lg' | 'xl' | stringfalseDrop shadow preset or raw CSS box-shadow.
glowboolean | stringfalseOuter glow halo. true uses user.primaryColor.
glowIntensitynumber20Glow radius in px (0–80).
blurnumber0backdrop-filter blur in px (pair with semi-transparent background).
opacitynumber10–1 opacity multiplier on the card.
hover'none' | 'lift' | 'glow' | 'tilt' | 'scale''none'Built-in hover micro-interaction.
hoverScalenumber1.02Scale factor when hover='scale' or 'lift'.
animate'none' | 'fade' | 'slide-up' | 'slide-down' | 'zoom' | 'flip''none'Mount entry animation (one-shot).
animateDelaynumber0Delay in ms before entry animation fires.
widthnumber | stringFixed width (number = px, string = any CSS unit).
maxWidthnumber | string440Max width cap.
fullWidthbooleanfalseStretch to 100% of parent. Overrides width/maxWidth.
direction'row' | 'column''column'Flex direction for children.
align'start' | 'center' | 'end' | 'stretch''stretch'align-items shortcut.
justify'start' | 'center' | 'end' | 'between' | 'around''start'justify-content shortcut.
gapnumber0Flex gap between direct children in px.
as'div' | 'section' | 'article' | 'main' | 'aside''div'Semantic tag for the wrapper.
interactivebooleanfalseAdds cursor:pointer + focus ring; emits onPress.
onPress(e: MouseEvent) => voidClick handler. Keyboard activation via Enter/Space when interactive.
styleCSSPropertiesEscape hatch; merged last.
minimal.tsx
<Card>
  <Bio />
</Card>
frosted-glass.tsx
<Card
  blur={20}
  background="rgba(255,255,255,0.06)"
  border="1px solid rgba(255,255,255,0.12)"
  rounded="xl"
  shadow="lg"
  glow
  glowIntensity={36}
  padding={32}
  gap={16}
>
  <Avatar size={120} ring />
  <Bio size={18} align="center" />
</Card>
interactive.tsx
<Card
  interactive
  hover="lift"
  animate="slide-up"
  animateDelay={120}
  onPress={() => navigate("/about")}
>
  <Bio>Tap me</Bio>
</Card>

Pegadinhas

gradient sobrescreve background; rounded sobrescreve radius; fullWidth ignora width/maxWidth. Os click handlers só disparam quando interactive é true (Enter/Espaço também).

<Avatar />

Foto do usuário. Fallback pra user.avatar, depois /default-avatar.png. Sempre círculo por padrão; troca com shape ou rounded.

PropTypeDefaultDescription
srcstringImage URL. Falls back to user.avatar, then /default-avatar.png.
sizenumber96Width/height in px.
altstringuser.displayName ?? user.username ?? ''Accessibility text.
shape'circle' | 'square' | 'rounded' | 'hex''circle'Frame shape. rounded=24px, hex uses clip-path.
roundedboolean | numbertruetrue=circle, false=square, number=exact border-radius. Overrides shape when set.
borderstring | number0Border. Number = width in px (uses primaryColor); string = full CSS border.
borderColorstringuser.primaryColorBorder color when border is numeric.
glowboolean | stringfalseOuter glow. true uses primaryColor.
glowIntensitynumber20Glow radius in px.
ringboolean | { color?, width?, offset? }falseOffset ring around the avatar (Discord-style).
status'online' | 'idle' | 'dnd' | 'offline' | nullnullStatus dot in the bottom-right.
shadowboolean | 'sm' | 'md' | 'lg'falseDrop shadow preset.
hover'none' | 'scale' | 'tilt' | 'spin''none'Hover micro-interaction.
hoverScalenumber1.05Scale on hover when hover='scale'.
fallbackstring'/default-avatar.png'URL used when src and user.avatar are both empty.
lazybooleantrueSet loading='lazy' on the <img>.
decoding'sync' | 'async' | 'auto''async'decoding hint passed to <img>.
onClick(e: MouseEvent) => voidClick handler.
styleCSSPropertiesEscape hatch.
default.tsx
<Avatar />
themed.tsx
<Avatar
  size={140}
  border={3}
  ring={{ color: "#46FF6E", width: 2, offset: 4 }}
  shadow="lg"
  status="online"
/>
hex-with-hover.tsx
<Avatar
  shape="hex"
  size={160}
  hover="spin"
  glow
  glowIntensity={32}
/>

Pegadinhas

rounded ganha de shape quando ambos estão setados. lazy default é true — desabilita pro avatar principal acima da dobra pra evitar flash.

<Bio />

Renderiza o texto da bio do usuário. Se children, user.bio E fallback estão todos vazios, retorna null (sem <p> nenhum).

PropTypeDefaultDescription
childrenReactNodeOverride text. If omitted, reads from user.bio.
sizenumber16font-size in px.
colorstring'rgba(255,255,255,0.75)'Text color.
as'p' | 'div' | 'span' | 'h1' | 'h2' | 'h3''p'Semantic tag.
align'left' | 'center' | 'right' | 'justify''left'Text alignment.
weight300 | 400 | 500 | 600 | 700 | 800 | 900400font-weight.
italicbooleanfalsefont-style: italic.
lineHeightnumber1.5line-height multiplier.
letterSpacingnumber0letter-spacing in px.
maxLinesnumberClamp to N lines with ellipsis (uses -webkit-line-clamp).
gradient[string,string] | { from, to, angle? }Gradient fill via background-clip: text.
glowboolean | stringfalseText-shadow glow. true uses primaryColor.
shadowboolean | stringfalsePlain text-shadow (non-glow).
uppercasebooleanfalsetext-transform: uppercase.
typingboolean | { speed?, loop?, cursor? }falseTypewriter animation. Reads from user.bio if children empty.
fallbackstring''Used if user.bio is empty and no children (instead of rendering null).
styleCSSPropertiesEscape hatch.
default.tsx
<Bio />
gradient-headline.tsx
<Bio
  as="h1"
  size={42}
  weight={800}
  align="center"
  gradient={["#46FF6E", "#11461D"]}
/>
typewriter.tsx
<Bio
  typing={{ speed: 40, cursor: true, loop: false }}
  size={20}
  align="center"
/>

Pegadinhas

gradient usa background-clip: text — precisa de um color fallback visível pra browsers antigos (Safari < 14). Faz clamp com maxLines só em valores block-level de as.

<Background />

Monta sua imagem/vídeo de background na raiz via uma camada persistente — navegar entre rotas NÃO remonta o background, então vídeos não reiniciam e imagens não piscam.

PropTypeDefaultDescription
childrenReactNodeContent rendered above the background layer.
srcstringImage or video URL. Falls back to user.backgroundImage.
blurnumber0filter: blur(px) on the bg layer.
dimnumber0Black overlay 0–1 (legacy; prefer `overlay`).
fit'cover' | 'contain' | 'fill' | 'tile''cover'object-fit / background-size mode.
position'center' | 'top' | 'bottom' | 'left' | 'right' | string'center'Image position; raw CSS strings also accepted.
overlaystring | { color?, opacity?, gradient?, angle? }Tint overlay above the image. Replaces `dim` for more control.
brightnessnumber1CSS filter brightness multiplier.
saturationnumber1CSS filter saturate multiplier.
contrastnumber1CSS filter contrast multiplier.
hueRotatenumber0CSS filter hue-rotate in degrees.
grayscalenumber00–1 grayscale amount.
loopbooleantrueLoop the <video>.
mutedbooleantrueMute the <video>. Required for autoplay in most browsers.
autoPlaybooleantrueAutoplay the <video>.
playbackRatenumber1Video playback speed.
parallaxboolean | numberfalseParallax scroll. Number = strength (0–1).
kenBurnsbooleanfalseSlow zoom/pan effect for stills (ignored on video).
gradient[string,string] | { from, to, angle? }Layered gradient. Replaces background if src is empty.
fixedbooleanfalsebackground-attachment: fixed.
fallbackstringImage URL used when src AND user.backgroundImage are empty.
styleCSSPropertiesEscape hatch (applied to wrapper).
default.tsx
<Background />
video-with-tint.tsx
<Background
  src="/my-loop.mp4"
  overlay={{ color: "#000", opacity: 0.45 }}
  brightness={0.9}
  saturation={1.2}
  playbackRate={0.85}
/>
gradient-only.tsx
<Background
  gradient={{ from: "#0f172a", to: "#46FF6E", angle: 135 }}
  kenBurns
/>

Pegadinhas

Como o background é persistente, colocar <Background> diferentes em rotas diferentes faz o último montado ganhar (last-in-wins via useLayoutEffect). Pra backgrounds por rota, monta um <Background> na raiz e troca o src com useRoute().

04

Dados & hooks

useUser() retorna esse shape — e esse shape. Email, badges, flags de premium, músicas e outros campos privados são intencionalmente não expostos.

DevModeUser.ts
interface DevModeUser {
  username: string;
  displayName: string | null;
  avatar: string | null;
  bio: string | null;
  backgroundImage: string | null;
  primaryColor: string | null;
  socialLinks: Array<{
    platform?: string;
    label?: string;
    url: string;
    isVisible?: boolean;
  }>;
}

Hook precisa rodar dentro da árvore do runtime

Se você chamar useUser() fora da árvore montada pelo runtime, ele lança o erro literal:
useUser() chamado fora do <Profile>. Garanta que o componente raiz é renderizado pelo runtime.
Em português: "useUser() chamado fora do <Profile>. Garanta que o componente raiz é renderizado pelo runtime."
hook-usage.tsx
import { useUser, useRoute, navigate } from "cee";

export default function Profile() {
  const user = useUser();          // never null — throws if misused
  const route = useRoute();        // { p: string | null, params: {...} }
  return (
    <div>
      <h1>@{user.username}</h1>
      <p>You're on: {route.p ?? "/"}</p>
      <button onClick={() => navigate("/about")}>Go to about</button>
    </div>
  );
}

useRoute() nunca lança — retorna { p: null, params: {} } por padrão. navigate(to) posta uma mensagem pro pai que valida o to contra /^[?#/]/ ou string vazia. URLs externas ou javascript: são silenciosamente descartadas.

05

Roteamento

Dois jeitos de dirigir perfis multi-rota:

  1. Single-page: só export default function — seu componente cuida de toda a lógica de roteamento via useRoute().
  2. Routes map: export const routes = { ... } — o runtime escolhe o componente que casa pra você.

As rotas são dirigidas por ?p=<path> na URL pra funcionar num slug de perfil estático tipo cee.bio/yourname.

routes-map.tsx
import { Card, Bio, Link, useRoute } from "cee";

function Home() {
  return <Card><Bio /><Link to="/about">About </Link></Card>;
}
function About() {
  return <Card><h2>About me</h2><Link to="/"> Back</Link></Card>;
}
function NotFound() {
  return <Card><p>Not found.</p><Link to="/">Home</Link></Card>;
}

export const routes = {
  "/": Home,
  "/about": About,
  default: NotFound,
};

Prioridade do pickComponent:

  1. exports.routes['/' + key] depois exports.routes[key] depois exports.routes['/'] (se não tem p) depois exports.routes.default
  2. exports.default
  3. exports.Profile
  4. exports em si se for uma função

06

Exemplos & cookbook

Copia, cola, publica. Todos os snippets são arquivos main.tsx sintaticamente válidos.

Perfil mínimo

main.tsx
import { Card, Avatar, Bio, SocialLinks, Background } from "cee";

export default function Profile() {
  return (
    <>
      <Background />
      <Card padding={32} gap={16} align="center">
        <Avatar size={120} ring />
        <Bio size={18} align="center" />
        <SocialLinks size={48} gap={14} />
      </Card>
    </>
  );
}

Card de vidro com blur fosco

main.tsx
import { Card, Avatar, Bio, SocialLinks, Background } from "cee";

export default function Profile() {
  return (
    <>
      <Background brightness={0.7} saturation={1.1} />
      <Card
        blur={24}
        background="rgba(255,255,255,0.06)"
        border="1px solid rgba(255,255,255,0.15)"
        rounded="xl"
        shadow="xl"
        glow
        glowIntensity={40}
        padding={36}
        gap={20}
        animate="fade"
        align="center"
      >
        <Avatar size={140} border={2} />
        <Bio as="h1" size={28} weight={700} align="center" />
        <SocialLinks size={48} colorMode="brand" hover="lift" />
      </Card>
    </>
  );
}

Background de vídeo com tint de overlay

main.tsx
import { Card, Avatar, Bio, Background } from "cee";

export default function Profile() {
  return (
    <>
      <Background
        src="/loop.mp4"
        overlay={{ gradient: ["#000", "rgba(70,255,110,0.3)"], angle: 200, opacity: 0.6 }}
        playbackRate={0.8}
      />
      <Card padding={40} align="center" gap={16}>
        <Avatar size={160} ring glow />
        <Bio
          as="h1"
          size={36}
          weight={800}
          align="center"
          gradient={["#fff", "#46FF6E"]}
        />
      </Card>
    </>
  );
}

Grid de pills sociais com stagger

main.tsx
import { Card, SocialLinks } from "cee";

export default function Profile() {
  return (
    <Card padding={28} gap={20}>
      <SocialLinks
        direction="grid"
        columns={3}
        size={64}
        gap={12}
        shape="rounded"
        colorMode="brand"
        showLabel
        labelPosition="bottom"
        hover="bounce"
        shadow="md"
        animate="stagger"
        animateDelay={70}
      />
    </Card>
  );
}

Bio em máquina de escrever

main.tsx
import { Card, Avatar, Bio } from "cee";

export default function Profile() {
  return (
    <Card padding={32} align="center" gap={14}>
      <Avatar size={110} />
      <Bio
        typing={{ speed: 35, cursor: true }}
        size={22}
        weight={600}
        align="center"
      />
    </Card>
  );
}

Layout duas colunas paired-profile

main.tsx
import { Card, Avatar, Bio, SocialLinks } from "cee";

export default function Profile() {
  return (
    <Card direction="row" gap={32} padding={32} fullWidth>
      <Card padding={24} align="center" gap={12} maxWidth={300}>
        <Avatar size={100} />
        <Bio align="center" />
        <SocialLinks size={36} />
      </Card>
      <Card padding={24} gap={12}>
        <h2 style={{ fontSize: 20, fontWeight: 700, color: "#fff" }}>Notes</h2>
        <Bio>Drop anything you want on the right column.</Bio>
      </Card>
    </Card>
  );
}

Overlay de entrada full-screen → revela card

main.tsx
import { useEffect, useState } from "react";
import { Card, Avatar, Bio, SocialLinks } from "cee";

export default function Profile() {
  const [entered, setEntered] = useState(false);
  useEffect(() => {
    const t = setTimeout(() => setEntered(true), 1200);
    return () => clearTimeout(t);
  }, []);

  if (!entered) {
    return (
      <div
        style={{
          position: "fixed", inset: 0, display: "grid", placeItems: "center",
          background: "#000", zIndex: 50,
        }}
      >
        <p style={{ fontSize: 14, letterSpacing: "0.4em", color: "#46FF6E" }}>
          LOADING
        </p>
      </div>
    );
  }
  return (
    <Card animate="zoom" padding={32} gap={16} align="center">
      <Avatar size={120} ring />
      <Bio align="center" />
      <SocialLinks />
    </Card>
  );
}

Card de estatísticas estilo analytics

main.tsx
import { Card, Bio, useUser } from "cee";

function Stat({ label, value }) {
  return (
    <Card padding={16} align="center" maxWidth={140} gap={4} background="rgba(70,255,110,0.06)">
      <Bio as="span" size={26} weight={800} color="#46FF6E">{value}</Bio>
      <Bio as="span" size={11} uppercase letterSpacing={1.5}>{label}</Bio>
    </Card>
  );
}

export default function Profile() {
  const u = useUser();
  return (
    <Card padding={28} gap={16}>
      <Bio as="h1" size={28} weight={800}>@{u.username}</Bio>
      <Card direction="row" gap={12} fullWidth>
        <Stat label="Songs" value="124" />
        <Stat label="Drops" value="8" />
        <Stat label="Fans" value="2.1k" />
      </Card>
    </Card>
  );
}

Tab switcher consciente da rota

main.tsx
import { Card, Link, useRoute } from "cee";

function Tab({ to, label, active }) {
  return (
    <Link
      to={to}
      variant={active ? "button" : "ghost"}
      rounded
      size="sm"
      background={active ? "#46FF6E" : undefined}
      color={active ? "#000" : "#fff"}
    >
      {label}
    </Link>
  );
}

export default function Profile() {
  const { p } = useRoute();
  const tab = p ?? "/";
  return (
    <Card padding={24} gap={16}>
      <Card direction="row" gap={8}>
        <Tab to="/" label="Home" active={tab === "/"} />
        <Tab to="/links" label="Links" active={tab === "/links"} />
        <Tab to="/about" label="About" active={tab === "/about"} />
      </Card>
      <div>
        {tab === "/" && <p>Home content.</p>}
        {tab === "/links" && <p>Links content.</p>}
        {tab === "/about" && <p>About content.</p>}
      </div>
    </Card>
  );
}

07

Imports & resolução de módulos

O que dá pra importar:

  • react, react-dom, react-dom/client — ligados ao React vendorizado do runtime.
  • cee — a stdlib (componentes + hooks + navigate + React).
  • Qualquer caminho relativo dentro do seu projeto: ./Hero, ../lib/format, ./pages/About.tsx.

Imports bare de npm (lodash, framer-motion, etc.) falham em tempo de compilação. Sem esm.sh, sem unpkg — por design, pra manter a supply chain trancada.

Fallback de extensão: match exato → .tsx.ts.jsx.jsfolder/index.*.

multi-file project
// main.tsx
import { Card } from "cee";
import { Hero } from "./Hero";
import { fmt } from "./lib/format";

export default function Profile() {
  return <Card><Hero label={fmt("hi")} /></Card>;
}

// Hero.tsx
export function Hero({ label }) { return <h1>{label}</h1>; }

// lib/format.ts  (resolved as lib/format → lib/format.ts)
export function fmt(s: string) { return s.toUpperCase(); }

Limites duros

Projeto-fonte ≤ 250KB · bundle compilado ≤ 200KB · mensagem de erro de runtime clipada em 4000 chars.

08

Problemas comuns

Esperava `export default function ...` ou `export const routes = {...}` no seu código.

O runtime montou mas não achou nada pra renderizar. Adiciona um export default function Profile(), um export function Profile(), ou um export const routes = { ... }.

useUser() chamado fora do <Profile>. Garanta que o componente raiz é renderizado pelo runtime.

Você chamou useUser() de um helper de escopo de módulo ou antes do runtime te montar. Move a chamada pra dentro de um componente que o runtime vai renderizar (é o que retorna <Profile /> de routes ou do export default).

Bundle muito grande (compilado > 200KB)

Sua saída CJS passou de 200KB. Divide arquivos, tira blobs grandes de dados inline (move pra fetches em runtime se precisar) e evita copiar libs no seu projeto — não tem tree-shaking além de eliminação de código morto, e o React já é provido externamente.

Recuperação de draft no localStorage

O editor autosalva seu projeto no localStorage a cada ~400ms. Se você fechar a aba no meio da edição, seu draft é restaurado na próxima abertura. Pra forçar um start limpo, limpa os dados do site ou usa janela anônima.

"Por que a tela tá cinza?"

Ou você não tem um default export, ou seu componente retornou null, ou seu CSS de background tá escondendo tudo. Abre o DevTools → checa o console por uma mensagem cee:error.

"Background não muda entre rotas"

By design. O background vive fora da árvore de rotas pra vídeos não reiniciarem. Pra backgrounds por rota, monta um <Background> na raiz e troca o src baseado em useRoute().p.

"Expected object, received null"

Essa string NÃO é um erro de runtime do Dev Mode — não aparece em lugar nenhum do código do runtime do cee.bio. Se você vê ela, veio do endpoint /users/update ao salvar um preset. Reporta via Discord ou pelo formulário de contato — inclui o payload da request e o body da response.

09

Segurança & limites

  • iframe sandbox: allow-scripts allow-popups — sem allow-same-origin, então o iframe ganha uma origin opaca. Cookies e storage isolados; não consegue ler a auth do cee.bio.
  • Permissions-Policy: allow="" zera todas as features delegadas (câmera, mic, geolocalização, payment, USB, etc).
  • Referrer: referrerPolicy="no-referrer" no iframe.
  • Badge anti-phishing persistente: "Custom por @user" é renderizado FORA do iframe no z-index 2147483647. Código do usuário não consegue desenhar por cima nem falsificar a chrome do cee.bio.
  • Validação de navegação: <Link to="..."> é repassado pro pai que re-valida contra /^[?#/]/ ou vazio. URLs javascript:/data:/externas são derrubadas na camada pai mesmo se você passar do filho.
  • CSP + React vendorizado: O React é servido do mesmo origin em /vendor/devmode-react.js. Sem esm.sh, sem unpkg, sem supply chain de CDN.
  • Origin de mensagem cross-frame: os dois lados forçam ev.source === window.parent / iframeRef.current?.contentWindow — defesa em profundidade.

Se você encontrar um problema (XSS-no-sandbox, cover-up do badge, spoof de mensagem do pai, etc.), por favor reporta via Discord. Não abre issue público antes da gente ter chance de corrigir.

10

Referência: erros & mensagens

Tipos de postMessage

TipoDireçãoPayload
cee:readyiframe → parent(none) — iframe is alive
cee:mountparent → iframe{ code, user, route }
cee:mountediframe → parent(none) — mount succeeded
cee:userparent → iframe{ user } (live update)
cee:routeparent → iframe{ route } (live update)
cee:erroriframe → parent{ message } (clipped to 4000 chars)
cee:navigateiframe → parent{ to } (parent re-validates)
cee:helloparent → iframe(handshake, optional)

Strings de erro de runtime (literais)

Todos os erros são em português; explicação em inglês abaixo de cada um.

Esperava `export default function ...` ou `export const routes = {...}` no seu código.

EN: Expected `export default function ...` or `export const routes = {...}` in your code. — pickComponent não achou nada renderizável.

useUser() chamado fora do <Profile>. Garanta que o componente raiz é renderizado pelo runtime.

EN: useUser() called outside <Profile>. Ensure your root component is rendered by the runtime.

Bundle muito grande

EN: Bundle too large. — O CJS compilado passou de 200KB; o iframe recusou montar.

11

CLI local: @cee.bio/cli

Editor local com hot reload, push & pull do seu projeto Dev Mode.

Pra quem prefere editar localmente com seu próprio editor (VSCode, Cursor, Neovim), publicamos um CLI em @cee.bio/cli. Ele dá scaffold de um projeto baseado em Vite + React, sincroniza com o cee.bio e tem hot reload com a preview do seu profile real (não mock).

Scaffold (1 comando)

Cria um projeto novo num diretório vazio. Não precisa instalar nada global — o npx resolve. O scaffold já gera .cee/project.json com um id estável: a partir do segundo push, o template é atualizado em vez de duplicado.

terminal
npx create-cee-profile@latest my-profile
cd my-profile
npm install
npm run login   # vincula sua conta cee.bio (uma vez por máquina)
npm run dev     # localhost:5173 com hot reload

Comandos

  • npm run login — vincula sua conta cee.bio via pairing code: o CLI abre um link no browser, você confirma logado, e o token é gravado em ~/.cee/credentials.json (chmod 600). Cada máquina ganha um token próprio, revogável independentemente.
  • npm run dev — sobe o Vite local na porta 5173 com hot reload. Antes de subir, faz fetch do seu profile real (GET /cli/profile) e escreve em src/.cee-user.json: o preview renderiza com sua foto, bio, social_links e badges reais.
  • npm run push — compila com esbuild (mesma config do editor in-site) e envia. Limites: 250KB source / 800KB bundle. Por padrão, salva o source como template no dashboard com upsert por projectId (mesmo projeto = mesmo template, sempre atualizado). Use --no-template pra pular ou --draft pra enviar sem ativar.
  • npm run pull — baixa o source atual do servidor pra src/profile.tsx. Útil pra começar a partir do que tá no ar, ou recuperar uma versão depois de mexer demais local.
terminal
# Sempre use `npm run X`  existe um pacote npm não-relacionado
# chamado "cee" que sequestra `npx cee`. O scaffold inclui
# @cee.bio/cli como devDependency, então `npm run login` resolve
# o binário local correto.

npm run login    # vincula esta máquina (pairing code via browser)
npm run dev      # Vite dev server + hot reload, com seu profile real
npm run push     # builda + publica em cee.bio/seu-user
npm run pull     # baixa o source atual do servidor
Os comandos rodam via npm run porque existe um pacote npm não-relacionado chamado cee que sequestra npx cee. O scaffold inclui @cee.bio/cli como devDependency, e npm run resolve o binário local correto. Mesmas validações e sandbox do editor web valem aqui — o bundle que entra em produção é idêntico.

Pronto pra começar?

Tudo aqui vive atrás de um toggle. Liga ele e começa a escrever JSX — sem instalar nada, sem boilerplate, sem ejetar.

Abrir o editor