Gestão de serviços · Novo recurso

Sistema de design ServiceTitan Anvil.

Torne o Anvil2 concreto com seu modelo de tokens em três níveis, valores de função verificados para os modos claro e escuro e uma interação completa de atribuição de serviço, com estados sincronizados de técnico, horário e revisão.

Guia OpenDesign7 capítulos38 entradas de referênciaGuia de aplicação original
01

Prévia da interface do sistema de design ServiceTitan Anvil

Alterne entre os modos claro e escuro, selecione a equipe disponível e revise um plano local sincronizado.

Associe um técnico a uma visita de serviçoAbrir exemplo completo
Original HTML com valores de função do token Hammer 3.5.0. · Referência de implementação
02

Referência de design do sistema de design ServiceTitan Anvil

Exportações do tema principal de @servicetitan/hammer-token 3.5.0, com a documentação de temas do Anvil2.

Pares semânticos para os modos claro e escuro

Fonte
Nome / RegraValor / ConfiguraçãoUso
BackgroundColorDefaultlight #ffffff / dark #1a1a1aSuperfície principal do aplicativo.
BackgroundColorDefaultHoverlight #f9f9f9 / dark #292929Superfície ao passar o cursor.
BackgroundColorPrimarylight #0265dc / dark #70b1ffPreenchimento da ação principal.
BackgroundColorPrimaryHoverlight #1d4ca3 / dark #9ecaffFunção nativa de estado de hover primário.
BackgroundColorPrimaryFaintlight #fafcff / dark #000b19Superfície selecionada discreta.
ForegroundColorSubduedlight #707070 / dark #c4c4c4Texto de apoio.
ForegroundColorOnPrimarylight #ffffff / dark #1a1a1aTexto combinado com preenchimento primário.
ForegroundColorPrimarylight #0265dc / dark #70b1ffPrimeiro plano interativo.
BorderColorSubduedlight #e6e6e6 / dark #404040Limites com pouca ênfase.
BorderColorPrimarylight #0265dc / dark #9ecaffLimite interativo.
StatusColorInfolight #0265dc / dark #70b1ffEstado informativo.
StatusColorDangerlight #d62100 / dark #ff5b3dAtenção imediata.
StatusColorSuccesslight #077e50 / dark #0bd085Revisão local concluída.
StatusColorWarninglight #ffbe00 / dark #ffe278Alerta não crítico.

Geometria primitiva e valores responsivos

Fonte
Nome / RegraValor / ConfiguraçãoUso
BreakpointSm640pxPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
BreakpointMd768pxPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
BreakpointLg1024pxPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
BreakpointXl1280pxPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
BreakpointXxl1536pxPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
DurationDefault200msPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
DurationInstant0msPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
DurationFast100msPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
DurationSlow300msPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
Radius00remPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
Radius10.1875remPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
Radius20.375remPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
Radius30.5625remPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
Radius40.75remPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
Radius50.9375remPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
Radius61.125remPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
Radius71.3125remPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.
Radius81.5remPrimitivo publicado; as funções semânticas e os aliases de componentes determinam as funções efetivamente aplicadas.

Contrato de substituição do ThemeProvider

Fonte
Nome / RegraValor / ConfiguraçãoUso
theme.semanticsemantic token overridesAltere uma função, como BackgroundColorPrimary.
theme.componentcomponent token overridesRestrinja uma alteração à função de um componente, como ButtonPrimaryBackgroundColor.
valuedefault/light token valueDefina o valor para o modo claro.
extensions.appearance.dark.valuedark appearance valueEspecifique o valor correspondente para o modo escuro.
CSS output--a2-<kebab-case-name>O ThemeProvider serializa a chave do token em uma variável CSS.
Primitive cascaderesolved at build timeSubstituir um primitivo em tempo de execução não recalcula os aliases semânticos.
03

Guia completo do sistema de design ServiceTitan Anvil

Um guia prático para escolher, aplicar e avaliar este sistema em uma interface real.

DESIGN-servicetitan-anvil.md

Orientações completas, um ponto de partida para a implementação e links para o código-fonte.

Baixar o guia

Arquitetura de tokens em três camadas do sistema de design ServiceTitan Anvil

Mantenha os primitivos abaixo das funções semânticas

O Anvil2 separa primitivos brutos, funções semânticas e aliases específicos de componentes. Em geral, um produto deve escolher a função que descreve o significado na interface, em vez de copiar um azul primitivo para cada controle. A tabela de referência inclui os valores exportados exatos e seus equivalentes para o modo escuro, permitindo revisar a mudança de tema como um par.

Com base neste guia de OpenDesign

Não espere que os aliases sejam recalculados em tempo de execução

O guia oficial de temas informa que as referências a primitivos são resolvidas durante a compilação. Alterar um primitivo por meio de um provedor em tempo de execução não regenera automaticamente a camada semântica. Para mudar as cores do aplicativo, substitua a função semântica relevante; para alterar um componente, use a camada de componentes. Isso evita um tema parcialmente alterado, que deixaria controles relacionados inconsistentes.

Modo de aparência claro e escuro do sistema de design ServiceTitan Anvil

Altere em conjunto o primeiro plano e o plano de fundo

A ação primária usa BackgroundColorPrimary com ForegroundColorOnPrimary. No modo claro, essa combinação usa azul com texto branco; no modo escuro, usa um azul mais claro com um rótulo escuro. Alterar apenas o preenchimento criaria um par incoerente. O estudo completo de HTML usa light-dark() com um seletor color-scheme para demonstrar esses valores, sem afirmar que é o ThemeProvider.

Use cores de estado com significados descritos

O Anvil2 associa significados de estado a famílias de cores. A revisão local usa uma função de sucesso, mas ainda informa exatamente o que aconteceu. O técnico indisponível é identificado por texto e tem a opção de rádio desabilitada. Nenhuma tonalidade verde sugere que uma visita de serviço foi despachada; o exemplo nunca se conecta a um backend de despacho.

Atribuição de serviço e estados no sistema de design ServiceTitan Anvil

Faça com que a disponibilidade determine a próxima escolha

Ao selecionar um técnico, as opções de horário são substituídas pela disponibilidade fictícia dessa pessoa. A pessoa indisponível continua visível, mas não pode ser selecionada. Como há uma dependência entre dois controles, alterar o primeiro deve redefinir o segundo para um valor válido, em vez de manter um horário que não corresponde à disponibilidade da pessoa escolhida.

Separe o planejamento da execução

A revisão da atribuição registra um plano local e informa explicitamente que nenhuma visita foi despachada. Um fluxo de produção exigiria verificações de permissão, uma resposta de agenda atualizada e tratamento de conflitos antes da confirmação. O exemplo mantém esse limite visível e, ainda assim, oferece uma interação completa que pode ser testada e adaptada.

Densidade operacional e legibilidade no sistema de design ServiceTitan Anvil

Mantenha o contexto do trabalho próximo da seleção

Um resumo compacto do trabalho apresenta a tarefa, a duração, a área e a prioridade. As opções de técnico formam uma lista vertical legível, e a visita proposta aparece em um painel separado. A interface usa limites neutros e cores de destaque moderadas para que a seleção e a ação principal continuem fáceis de identificar. As barras da agenda estão identificadas como ilustrativas; o seletor de horário é a fonte oficial.

Preserve a ordem de leitura em dispositivos móveis

Integração nativa e alternativa completa de HTML para o sistema de design ServiceTitan Anvil

Use a estrutura documentada do provedor

O ThemeProvider do Anvil2 aceita substituições de tokens semânticos e de componentes, com extensões para valores e aparência escura. O guia inclui abaixo um exemplo completo do provedor, com fonte na documentação pública. Trata-se de uma referência de integração nativa, distinta do exemplo independente de HTML executado localmente.

Evite alegar um runtime de pacote não verificado

Esta página apresenta uma implementação funcional em HTML e dados de tokens verificados. O exemplo do provedor nativo é uma referência de integração separada, não o runtime exibido na prévia. Ao adotar componentes React, instale uma versão compatível do Anvil2, inclua os estilos necessários e verifique as APIs dos componentes antes de substituir os controles nativos de HTML.

Acessibilidade e verificação de estados no sistema de design ServiceTitan Anvil

Use a semântica nativa dos formulários

Os técnicos são apresentados como opções de rádio nativas com rótulos, o horário de início é um seletor identificado por rótulo e o controle de aparência expõe aria-pressed. A mensagem de revisão é uma região de status. O modelo nativo de teclado do navegador gerencia a seleção das opções de rádio; o estilo de foco visível é independente dos estilos de seleção ou de hover.

Verifique as alterações após a confirmação

Alterar o técnico ou o horário limpa as mensagens de revisão anteriores. Alternar a aparência mantém o técnico e o horário selecionados, pois uma mudança visual de tema não deve apagar um plano. Os testes devem abranger as duas transições, além da seleção desabilitada e do excesso de conteúdo em telas estreitas, em vez de apenas verificar se é possível clicar em um botão.

Transferência para agente de IA e implementação do sistema de design ServiceTitan Anvil

Comece pelo modelo completo de estados

Salve e execute o exemplo completo em HTML para inspecionar o comportamento. Os pares claro/escuro derivados dos tokens aparecem na parte superior do CSS, e os dados de disponibilidade definidos no exemplo estão no script. Uma implementação nativa de React pode manter as mesmas transições de estado e substituir os controles do Anvil2 após verificar sua compatibilidade.

  1. Execute o exemplo completo de HTML.
  2. Selecione o segundo técnico e confirme que as opções de horário de início mudam.
  3. Revise o plano, depois altere o horário e confirme que as mensagens são removidas.
  4. Alterne a aparência e confirme que a seleção atual é mantida.

Especifique o que exige dados reais

Peça ao agente de IA que mantenha a lista de horários dependente do técnico, o membro da equipe indisponível desabilitado, a invalidação da revisão e o comportamento da aparência. Substitua o trabalho e a equipe fictícios somente quando houver um contrato de dados real. Não transforme a revisão local em uma alegação de sucesso apenas mudando o rótulo do botão.

  • Verifique a disponibilidade antes de realizar um despacho.
  • Mantenha separadas as substituições semânticas e as de componentes.
  • Teste os pares de primeiro plano e plano de fundo nos modos claro e escuro.

Exemplos de componentes do sistema de design ServiceTitan Anvil

Exemplo completo de atribuição local

Original em HTML/CSS/JavaScript, com aparências derivadas de tokens e estado de formulário dependente.

html
<!doctype html>
<html lang="en">
  <meta charset="utf-8" /><meta
    name="viewport"
    content="width=device-width,initial-scale=1"
  /><title>Dispatch assignment study</title
  ><style>
    :root {
      color-scheme: light;
      --surface: light-dark(#fff, #1a1a1a);
      --hover: light-dark(#f9f9f9, #292929);
      --text: light-dark(#040404, #fff);
      --muted: light-dark(#707070, #c4c4c4);
      --line: light-dark(#e6e6e6, #404040);
      --action: light-dark(#0265dc, #70b1ff);
      --on-action: light-dark(#fff, #1a1a1a);
      --faint: light-dark(#fafcff, #000b19);
      --success: light-dark(#077e50, #0bd085);
    }
    * {
      box-sizing: border-box;
    }
    body {
      margin: 0;
      padding: 24px;
      background: var(--surface);
      color: var(--text);
      font:
        16px/1.5 system-ui,
        sans-serif;
    }
    h1 {
      font-size: 28px;
      margin: 8px 0;
    }
    h2 {
      font-size: 20px;
      margin: 0 0 16px;
    }
    p {
      margin: 8px 0 16px;
    }
    .small {
      font-size: 13px;
      color: var(--muted);
    }
    header {
      display: flex;
      justify-content: space-between;
      gap: 24px;
      border-bottom: 1px solid var(--line);
      padding-bottom: 24px;
    }
    button,
    select {
      font: inherit;
      color: inherit;
      background: var(--surface);
      border: 1px solid var(--line);
      border-radius: 4px;
      padding: 10px 16px;
      min-height: 44px;
    }
    button {
      cursor: pointer;
    }
    button:hover {
      background: var(--hover);
    }
    button:focus-visible,
    select:focus-visible,
    input:focus-visible {
      outline: 3px solid var(--action);
      outline-offset: 3px;
    }
    main {
      display: grid;
      grid-template-columns: 1.4fr 1fr;
      gap: 24px;
      margin-top: 24px;
    }
    .board {
      border: 1px solid var(--line);
      border-radius: 8px;
      padding: 24px;
    }
    .job {
      padding: 16px;
      background: var(--faint);
      border-left: 4px solid var(--action);
      margin-bottom: 24px;
    }
    .job strong {
      display: block;
    }
    .people {
      display: grid;
      gap: 12px;
    }
    .person {
      display: grid;
      grid-template-columns: 24px 1fr auto;
      gap: 12px;
      padding: 16px;
      border: 1px solid var(--line);
      border-radius: 4px;
      align-items: center;
    }
    .person:has(:checked) {
      border-color: var(--action);
      background: var(--faint);
    }
    input {
      accent-color: var(--action);
      width: 18px;
      height: 18px;
    }
    .person small {
      display: block;
      color: var(--muted);
    }
    .person:has(:disabled) {
      background: var(--hover);
    }
    .timeline {
      display: flex;
      gap: 4px;
      margin: 20px 0;
    }
    .timeline span {
      height: 40px;
      flex: 1;
      background: var(--line);
      border-radius: 4px;
    }
    .timeline .open {
      background: var(--action);
    }
    .timeline-label {
      display: flex;
      justify-content: space-between;
      font-size: 13px;
      color: var(--muted);
    }
    label[for="time"] {
      display: block;
      margin: 16px 0 8px;
    }
    .primary {
      background: var(--action);
      color: var(--on-action);
      border-color: var(--action);
      margin-top: 24px;
    }
    .primary:hover {
      filter: brightness(0.95);
      background: var(--action);
    }
    #feedback {
      color: var(--success);
      padding-top: 16px;
    }
    footer {
      border-top: 1px solid var(--line);
      padding-top: 16px;
      margin-top: 24px;
      font-size: 13px;
      color: var(--muted);
    }
    @media (max-width: 767px) {
      body {
        padding: 16px;
      }
      main {
        grid-template-columns: 1fr;
      }
      .board {
        padding: 16px;
      }
      header {
        display: block;
      }
      header button {
        margin-top: 8px;
      }
      .person {
        grid-template-columns: 24px 1fr;
      }
      .person > span:last-child {
        grid-column: 2;
      }
    }
  </style>
  <body>
    <header>
      <div>
        <div class="small">SERVICE OPERATIONS / LOCAL PLANNING STUDY</div>
        <h1>Assign a service visit</h1>
        <p>Match a sample job with an available technician.</p>
      </div>
      <div>
        <button id="theme" aria-pressed="false">Dark appearance</button>
      </div>
    </header>
    <main>
      <section class="board">
        <h2>Job and technician</h2>
        <div class="job">
          <strong>Sample job #204 · Heating inspection</strong
          ><span>90 minutes · North district · Routine priority</span>
        </div>
        <div class="people" id="people"></div>
      </section>
      <aside class="board">
        <h2>Proposed visit</h2>
        <p id="technician"></p>
        <label for="time">Available start time</label
        ><select id="time"></select>
        <div class="timeline" aria-hidden="true">
          <span></span><span class="open"></span><span class="open"></span
          ><span></span><span></span><span></span>
        </div>
        <div class="timeline-label">
          <span>08:00</span><span>Illustrative day</span><span>17:00</span>
        </div>
        <p class="small">
          The time list is the source of available choices. The bars are a
          decorative schedule summary.
        </p>
        <button id="assign" class="primary">Review assignment</button>
        <p id="feedback" role="status"></p>
      </aside>
    </main>
    <footer>
      Original HTML using Anvil2 Hammer token 3.5.0 light/dark roles. Fictional
      technicians and schedules; no dispatch service connected.
    </footer>
    <script>
      const staff = [
        {
          id: "alex",
          name: "Alex Morgan",
          skill: "Heating specialist",
          slots: ["09:00", "11:00"],
        },
        {
          id: "sam",
          name: "Sam Rivera",
          skill: "General maintenance",
          slots: ["13:00", "15:00"],
        },
        { id: "lee", name: "Lee Chen", skill: "Unavailable today", slots: [] },
      ];
      let selected = "alex";
      const $ = (id) => document.getElementById(id);
      function update() {
        const p = staff.find((s) => s.id === selected);
        $("technician").textContent = p.name + " · " + p.skill;
        $("time").replaceChildren(...p.slots.map((t) => new Option(t, t)));
        $("feedback").textContent = "";
      }
      $("people").replaceChildren(
        ...staff.map((p) => {
          const l = document.createElement("label");
          l.className = "person";
          const input = document.createElement("input");
          input.type = "radio";
          input.name = "technician";
          input.checked = p.id === selected;
          input.disabled = p.slots.length === 0;
          input.onchange = () => {
            selected = p.id;
            update();
          };
          const text = document.createElement("span");
          text.innerHTML = `<strong>${p.name}</strong><small>${p.skill}</small>`;
          const availability = document.createElement("span");
          availability.textContent = p.slots.length
            ? p.slots.length + " start times"
            : "Unavailable";
          l.append(input, text, availability);
          return l;
        }),
      );
      $("time").onchange = () => {
        $("feedback").textContent = "";
      };
      $("assign").onclick = () => {
        $("feedback").textContent =
          "Local plan: " +
          staff.find((s) => s.id === selected).name +
          " at " +
          $("time").value +
          ". No visit has been dispatched.";
      };
      $("theme").onclick = () => {
        let dark = $("theme").getAttribute("aria-pressed") !== "true";
        $("theme").setAttribute("aria-pressed", dark);
        document.documentElement.style.colorScheme = dark ? "dark" : "light";
      };
      update();
    </script>
  </body>
</html>

Referência de código

  • Salve como index.html; navegadores modernos oferecem suporte a light-dark(). Este não é o runtime nativo do Anvil2.

Referência de integração do ThemeProvider

Estrutura documentada de substituição semântica em uma entrada completa de React. Valide-a com a versão instalada do Anvil2.

jsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { ThemeProvider } from '@servicetitan/anvil2';

const theme = {
  semantic: {
    BackgroundColorPrimary: {
      value: '#7c3aed',
      extensions: { appearance: { dark: { value: '#a78bfa' } } },
    },
  },
};

function App() {
  return (
    <ThemeProvider theme={theme}>
      <main>
        <h1>Theme integration check</h1>
        <div style={{ background: 'var(--a2-background-color-primary)', padding: 24 }}>
          <span style={{ color: 'var(--a2-foreground-color-on-primary)' }}>
            Primary role with the provider’s matching foreground role
          </span>
        </div>
      </main>
    </ThemeProvider>
  );
}
createRoot(document.getElementById('root')).render(<App />);

Referência de código

  • Esta referência do provedor foi conferida com a documentação oficial; o exemplo em execução acima é independente de HTML.
04

Configuração do agente de IA para o sistema de design ServiceTitan Anvil

Ponto de partida para implementação

O exemplo executado não requer um pacote Anvil2. O exemplo separado do ThemeProvider exige uma instalação nativa compatível.

sh
# Save the complete HTML example as index.html
python3 -m http.server 8080
# Open http://localhost:8080

Um prompt para seu projeto

Prompt para agente de IA
Crie uma interface de atribuição de serviços usando funções semânticas do Anvil2. Mantenha os pares de primeiro plano para os modos claro e escuro, as opções de horário dependentes do técnico, a equipe indisponível desabilitada e a invalidação da revisão do exemplo completo. Escolha explicitamente entre o HTML original e uma instalação verificada de React do Anvil2. Nunca afirme que houve um despacho sem uma resposta do backend.
05

Fontes e escopo do sistema de design ServiceTitan Anvil

Um guia de OpenDesign para o Anvil2 da ServiceTitan. Os valores dos tokens foram extraídos do Hammer 3.5.0 público. O exemplo em execução é HTML original, não um runtime de React do Anvil2; todos os técnicos e dados de disponibilidade são fictícios.