Gestión de servicios · Nuevo recurso

Sistema de diseño ServiceTitan Anvil.

Concreta Anvil2 con su modelo de tokens de tres niveles, valores de roles verificados para los modos claro y oscuro, y una interacción completa de asignación de servicios con estados sincronizados de técnico, hora y revisión.

Guía de OpenDesign7 capítulos38 entradas de referenciaGuía de aplicación original
01

Vista previa de la interfaz del sistema de diseño ServiceTitan Anvil

Cambia entre la apariencia clara y oscura, selecciona al personal disponible y revisa un plan local sincronizado.

Asigna un técnico a una visita de servicioAbrir ejemplo completo
HTML original con valores de función de tokens Hammer 3.5.0. · Referencia de implementación
02

Referencia de diseño del sistema de diseño ServiceTitan Anvil

Exportaciones del tema principal de @servicetitan/hammer-token 3.5.0, junto con la documentación de tematización de Anvil2.

Pares semánticos para los modos claro y oscuro

Fuente
Nombre / ReglaValor / ConfiguraciónUso
BackgroundColorDefaultlight #ffffff / dark #1a1a1aSuperficie principal de la aplicación.
BackgroundColorDefaultHoverlight #f9f9f9 / dark #292929Superficie al pasar el cursor.
BackgroundColorPrimarylight #0265dc / dark #70b1ffRelleno de acción principal.
BackgroundColorPrimaryHoverlight #1d4ca3 / dark #9ecaffFunción nativa de estado al pasar el cursor sobre el botón principal.
BackgroundColorPrimaryFaintlight #fafcff / dark #000b19Fondo seleccionado discreto.
ForegroundColorSubduedlight #707070 / dark #c4c4c4Texto complementario.
ForegroundColorOnPrimarylight #ffffff / dark #1a1a1aTexto combinado con el relleno principal.
ForegroundColorPrimarylight #0265dc / dark #70b1ffPrimer plano interactivo.
BorderColorSubduedlight #e6e6e6 / dark #404040Límites de bajo énfasis.
BorderColorPrimarylight #0265dc / dark #9ecaffLímite interactivo.
StatusColorInfolight #0265dc / dark #70b1ffEstado informativo.
StatusColorDangerlight #d62100 / dark #ff5b3dRequiere atención inmediata.
StatusColorSuccesslight #077e50 / dark #0bd085Revisión local completada.
StatusColorWarninglight #ffbe00 / dark #ffe278Precaución no crítica.

Geometría primitiva y valores adaptables

Fuente
Nombre / ReglaValor / ConfiguraciónUso
BreakpointSm640pxPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
BreakpointMd768pxPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
BreakpointLg1024pxPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
BreakpointXl1280pxPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
BreakpointXxl1536pxPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
DurationDefault200msPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
DurationInstant0msPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
DurationFast100msPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
DurationSlow300msPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
Radius00remPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
Radius10.1875remPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
Radius20.375remPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
Radius30.5625remPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
Radius40.75remPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
Radius50.9375remPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
Radius61.125remPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
Radius71.3125remPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.
Radius81.5remPrimitiva publicada; los alias semánticos o de componentes determinan las funciones reales de la aplicación.

Contrato de anulación de ThemeProvider

Fuente
Nombre / ReglaValor / ConfiguraciónUso
theme.semanticsemantic token overridesCambia una función como BackgroundColorPrimary.
theme.componentcomponent token overridesLimita un cambio a la función de un componente, por ejemplo, ButtonPrimaryBackgroundColor.
valuedefault/light token valueProporciona el valor para la apariencia clara.
extensions.appearance.dark.valuedark appearance valueEspecifica el valor correspondiente para la apariencia oscura.
CSS output--a2-<kebab-case-name>ThemeProvider serializa la clave del token en una variable CSS.
Primitive cascaderesolved at build timeAnular una primitiva en tiempo de ejecución no vuelve a calcular los alias semánticos.
03

Guía completa del sistema de diseño ServiceTitan Anvil

Una guía práctica para elegir, aplicar y revisar este sistema en una interfaz real.

DESIGN-servicetitan-anvil.md

Orientación completa, un punto de partida para la implementación y enlaces al código fuente.

Descargar la guía

Arquitectura de tokens en tres niveles del sistema de diseño ServiceTitan Anvil

Mantén las primitivas por debajo de las funciones semánticas

Anvil2 separa las primitivas sin procesar, las funciones semánticas y los alias específicos de componentes. Por lo general, un producto debería elegir la función que describa el significado de la interfaz, en lugar de copiar un azul primitivo en cada control. La tabla de referencia incluye los valores exportados exactos y sus equivalentes para el modo oscuro, de modo que se pueda revisar el tema como un par.

De esta guía de OpenDesign

No des por sentado que los alias se recalculan en tiempo de ejecución

La guía oficial de tematización indica que las referencias a primitivas se resuelven durante la compilación. Cambiar una primitiva mediante un proveedor en tiempo de ejecución no vuelve a generar automáticamente la capa semántica. Para cambiar los colores de la aplicación, anula la función semántica pertinente; para cambiar un componente, usa el nivel de componente. Así se evita que el tema cambie solo en parte y que los controles relacionados queden descoordinados.

Apariencia clara y oscura del sistema de diseño ServiceTitan Anvil

Combina los cambios de primer plano y de fondo

La acción principal usa BackgroundColorPrimary con ForegroundColorOnPrimary. En la apariencia clara, es azul con texto blanco; en la oscura, es un azul más claro con una etiqueta oscura. Cambiar solo el relleno daría lugar a una combinación incoherente. El estudio completo de HTML usa light-dark() con un selector color-scheme para mostrar esos valores, sin afirmar que sea ThemeProvider.

Asigna un significado explícito a los tonos de estado

Anvil2 asigna significados de estado a familias de colores. La revisión local usa una función de éxito, pero aun así indica con precisión lo que ocurrió. El técnico no disponible se identifica mediante texto y tiene un botón de opción deshabilitado. No se usa un matiz verde para insinuar que se ha enviado una visita de servicio; el ejemplo nunca se conecta a un backend de despacho.

Asignación de servicios y estados del sistema de diseño ServiceTitan Anvil

Haz que la disponibilidad determine la siguiente opción

Al seleccionar un técnico, las opciones de hora de inicio se actualizan para mostrar su disponibilidad ficticia. La persona no disponible sigue visible, pero no se puede seleccionar. Como se trata de una dependencia entre dos controles, al cambiar el primero, el segundo debe restablecerse a un valor válido, en lugar de conservar una hora que corresponda a otra persona.

Separa la planificación de la ejecución

La revisión de la asignación registra un plan local e indica explícitamente que no se ha enviado ninguna visita. Un flujo de trabajo de producción necesitaría comprobar los permisos, obtener una respuesta actualizada del horario y gestionar los conflictos antes de confirmar la asignación. El ejemplo mantiene visible ese límite y, a la vez, ofrece una interacción completa que se puede probar y adaptar.

Densidad operativa y legibilidad del sistema de diseño ServiceTitan Anvil

Mantén el contexto del trabajo cerca de la selección

Un resumen compacto del trabajo incluye la tarea, la duración, la zona y la prioridad. Las opciones de técnico forman una lista vertical fácil de leer, y la visita propuesta aparece en un panel independiente. La interfaz usa separadores neutros y un color de acento discreto para que sea fácil identificar la selección y la acción principal. Las barras del horario están etiquetadas como ilustrativas; el selector de hora es la fuente de verdad.

Conserva el orden de lectura en móviles

Integración nativa del sistema de diseño ServiceTitan Anvil y alternativa completa HTML

Usa la estructura documentada del proveedor

ThemeProvider de Anvil2 acepta sobrescrituras de tokens semánticos y de componentes, con extensiones para los valores y la apariencia oscura. La guía incluye a continuación un ejemplo completo del proveedor, basado en la documentación pública. Es una referencia de integración nativa, distinta de la muestra independiente de HTML que se ejecutó localmente.

Evita afirmar que un paquete se ejecuta si no se ha verificado

Esta página ofrece una implementación funcional de HTML y datos de tokens verificados. El ejemplo del proveedor nativo es una referencia de integración aparte, no el entorno de ejecución que se muestra en la vista previa. Al adoptar componentes de React, instala una versión de Anvil2 compatible, incluye los estilos requeridos y verifica las API de los componentes antes de sustituir los controles nativos de HTML.

Accesibilidad y verificación de estados del sistema de diseño ServiceTitan Anvil

Usa semántica nativa de formularios

Los técnicos se presentan como botones de opción nativos con etiquetas, la hora de inicio es un selector con etiqueta y el control de apariencia expone aria-pressed. El mensaje de revisión es una región de estado. El modelo de teclado nativo del navegador gestiona la selección de opciones; el estilo de foco visible es independiente de los estilos de selección y al pasar el cursor.

Comprueba los cambios después de confirmar

Cambiar de técnico o de hora borra los mensajes de revisión anteriores. Cambiar la apariencia conserva el técnico y la hora seleccionados, porque un cambio visual de tema no debería borrar un plan. Las pruebas deben cubrir ambas transiciones, además de la selección deshabilitada y el desbordamiento en pantallas estrechas, en vez de limitarse a comprobar que se puede hacer clic en un botón.

Traspaso e implementación del agente para el sistema de diseño ServiceTitan Anvil

Empieza con el modelo de estado completo

Guarda y ejecuta el ejemplo completo de HTML para inspeccionar su comportamiento. Los pares de valores claros y oscuros derivados de tokens aparecen en la parte superior de CSS, y los datos de disponibilidad definidos aparecen en el script. Una implementación nativa de React puede mantener las mismas transiciones de estado y sustituir los controles de Anvil2 tras verificarlos.

  1. Ejecuta la muestra completa de HTML.
  2. Selecciona al segundo técnico y verifica que cambien las opciones de hora de inicio.
  3. Revisa el plan, cambia la hora y verifica que se borre el mensaje.
  4. Cambia la apariencia y verifica que se conserve la selección actual.

Especifica qué requiere datos reales

Pide al agente que conserve la lista de horas dependiente del técnico, el miembro del personal deshabilitado, la invalidación de la revisión y el comportamiento de la apariencia. Sustituye el trabajo y el personal ficticios solo cuando haya un contrato de datos real. No conviertas la revisión local en una afirmación de éxito simplemente cambiando el texto del botón.

  • Comprueba la disponibilidad antes de asignar un servicio real.
  • Mantén separadas las sobrescrituras semánticas y las de componentes.
  • Prueba los pares de primer plano y fondo en los modos claro y oscuro.

Ejemplos de componentes del sistema de diseño ServiceTitan Anvil

Muestra completa de asignación local

HTML/CSS/JavaScript originales, con apariencias derivadas de tokens y estado de formulario dependiente.

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>

Referencia de código

  • Guárdalo como index.html; los navegadores modernos admiten light-dark(). Este no es el entorno de ejecución nativo de Anvil2.

Referencia de integración de ThemeProvider

Estructura documentada de sobrescritura semántica en una entrada completa de React. Valídala con la versión instalada de 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 />);

Referencia de código

  • Esta referencia del proveedor se verificó con la documentación oficial; la muestra en ejecución de arriba es una implementación independiente de HTML.
04

Configuración del agente para el sistema de diseño ServiceTitan Anvil

Punto de partida para la implementación

La muestra ejecutada no requiere un paquete de Anvil2. El ejemplo independiente de ThemeProvider requiere una instalación nativa compatible.

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

Una instrucción para tu proyecto

Instrucción para el agente
Crea una interfaz de asignación de servicios con roles semánticos de Anvil2. Conserva los pares de primer plano y fondo para los modos claro y oscuro, las opciones de hora dependientes del técnico, el personal no disponible deshabilitado y la invalidación de la revisión del ejemplo completo. Elige explícitamente entre HTML original y una instalación de React de Anvil2 verificada. Nunca afirmes que se ha asignado un servicio sin una respuesta del backend.
05

Fuentes y alcance del sistema de diseño ServiceTitan Anvil

Una guía de OpenDesign para ServiceTitan Anvil2. Los valores de los tokens se extraen de Hammer token 3.5.0 público. El ejemplo en ejecución está hecho con HTML original, no se ejecuta con React de Anvil2; todos los técnicos y datos de disponibilidad son ficticios.