frontend-observability – skill Thiết kế & UI cho agent AI
Mẫu quan sát (observability) phía client độc lập framework, có thể tái sử dụng cho tích hợp React hoặc React Native.
Cài skill frontend-observability bằng một lệnh
Chọn công cụ bạn đang dùng, copy lệnh và dán vào terminal. Skill sẽ nằm trong thư mục skills của công cụ đó và sẵn sàng dùng ở phiên làm việc tiếp theo.
Claude Code
mkdir -p ~/.claude/skills && curl -fsSL https://skill.dn1nguoi.com/z/frontend-observability-990f6.tgz | tar xz -C ~/.claude/skills
Gọi trong Claude Code: /frontend-observability – rồi mô tả công việc bạn muốn làm.
Codex
mkdir -p ~/.codex/skills && curl -fsSL https://skill.dn1nguoi.com/z/frontend-observability-990f6.tgz | tar xz -C ~/.codex/skills
Gọi trong Codex: $frontend-observability – rồi mô tả công việc bạn muốn làm.
Hermes Agent
mkdir -p ~/.hermes/skills && curl -fsSL https://skill.dn1nguoi.com/z/frontend-observability-990f6.tgz | tar xz -C ~/.hermes/skills
Gọi trong Hermes Agent: /frontend-observability – rồi mô tả công việc bạn muốn làm.
DeepSeek
mkdir -p ~/.deepcode/skills && curl -fsSL https://skill.dn1nguoi.com/z/frontend-observability-990f6.tgz | tar xz -C ~/.deepcode/skills
Gọi trong DeepSeek: /frontend-observability – rồi mô tả công việc bạn muốn làm.
Nội dung SKILL.md
Nội dung SKILL.md gốc bằng tiếng Anh.
Frontend Observability (the field side)
When to Use
Use this skill when you need a portable, framework-agnostic field-side observability pattern for React or React Native integrations. Defines a typed taxonomy and best-effort provider dispatch with explicit consent, privacy and runtime limitations.
> Portable skill — readable by Claude Code, OpenCode, Codex, Cursor, Windsurf, and others.
> This skill describes a field-side observability system — event taxonomy, provider fan-out,
> real-user vitals, error reporting, consent — not a dashboard or a specific vendor. It is the
> field complement to the frontend-lighthouse skill: Lighthouse is the _lab_ gate (synthetic,
> pre-merge); this is the _field_ (what real users actually experience). It lives in a
> services/analytics/ module per the frontend-architecture skill.
The goal: you can answer "what are real users doing, and what are they experiencing?" — with a
typed event vocabulary (no stringly-typed track("clicked_thing") scattered everywhere), a
fan-out that is best-effort (provider exceptions are isolated where supported), real **Core Web Vitals
from the field, and consent** respected before anything fires.
---
0. The five core ideas
1. Events are a typed vocabulary. Event names are canonical constants with a union type — never inline string literals. The taxonomy is reviewable in one file and the compiler rejects typos.
2. Fan-out is best-effort, not a scheduling guarantee. track() dispatches to every provider, each with synchronous and asynchronous error handling. A missing global, a thrown provider, an unloaded script — ordinary caught exceptions/rejections do not propagate; synchronous vendor work can still block the thread.
3. One entry point, SSR-safe. A single track(event, props) is the only way to record. It's reached through a context hook that no-ops outside a provider and on the server, so instrumented components render safely anywhere.
4. Field vitals complement lab budgets. Real-user LCP/INP/CLS are reported to the same fan-out. Lighthouse samples controlled conditions; field vitals estimate actual experiences for the observed population — together they close the loop.
5. Consent gates everything. No telemetry (events, vitals, error reports with PII) fires before opt-in. Consent state is checked at the fan-out boundary, not sprinkled through call sites.
---
Runtime and privacy contract
These are independent integration sketches, not complete compiled modules. Supply the missing globals, config, imports, Props and error classifier; pin vendor APIs. Try/catch cannot prevent synchronous CPU blocking, infinite loops or SDK background effects. Adapters must do bounded work, return their promises, avoid raw logging and share per-provider rate/queue limits. Use actual keyboard/SSR/unmount/rejection tests.
Allowlist property names, types and sizes before dispatch; TypeScript types do not validate runtime input. IDs can identify people; URLs may contain tokens or query PII. Send a route/category label rather than a raw href, error message or stack. Treat cross-vendor fan-out as separate data disclosures requiring the approved purposes and destinations.
A boolean track gate does not stop SDK auto-collection, cookies, session replay or queued sends. Configure collection disabled before native SDK startup, delay scripts/init until permitted, disable and clear provider state/queues on withdrawal, and recheck consent after every awaited initialization. A dynamic import may still execute module effects; choose SDK loading that satisfies actual requirements. Consent storage failures default to denied. Do not hydrate a module-global consent state from user requests on SSR. React Server Components cannot call this client hook; conventional SSR only uses the no-op outside the provider. Native vitals are not web Core Web Vitals, and React Native Firebase requires a compatible native build rather than arbitrary Expo Go.
1. Directory layout
The system is one service module plus its constants (per frontend-architecture).
src/
├── constants/
│ └── analytics.ts ← canonical event names + AnalyticsEvent union
├── services/analytics/
│ ├── index.ts ← barrel: track, adapters, types
│ ├── track.ts ← the best-effort fan-out (single entry point)
│ ├── adapters.ts ← one (event, props) => void per provider, window-guarded
│ ├── web-vitals.ts ← report real-user LCP/INP/CLS into track()
│ └── consent.ts ← consent gate read by the fan-out
├── providers/
│ └── AnalyticsProvider.tsx ← 'use client' context exposing useAnalytics().track
└── error/
└── ErrorBoundary.tsx ← reports caught render errors via the fan-out---
2. The event taxonomy (typed, never inline)
One file owns every event name. Components reference constants; the union type makes typos a compile
error and the catalog a single source of truth.
// constants/analytics.ts
export const ANALYTICS_EVENTS = {
PROJECT_CLICK: "project_click",
GITHUB_CLICK: "github_click",
RESUME_DOWNLOAD: "resume_download",
CONTACT_SUBMISSION: "contact_submission",
WEB_VITAL: "web_vital",
CLIENT_ERROR: "client_error",
} as const;
export type AnalyticsEvent =
(typeof ANALYTICS_EVENTS)[keyof typeof ANALYTICS_EVENTS];// CORRECT — typed constant, autocompletes, can't typo
track(ANALYTICS_EVENTS.GITHUB_CLICK, { url });
// WRONG — stringly-typed, drifts, no compile check
track("github-click"); // ❌ silently a different event from "github_click"Hard rules:
- No inline event-name strings anywhere; only
ANALYTICS_EVENTS.*. - Event names are snake_case and stable — renaming one breaks historical dashboards, so treat the catalog as a contract.
- Keep
propsshapes small and PII-light (see §6); prefer ids over names, never raw emails.
---
3. Best-effort fan-out with bounded adapter work
track() is the single entry point. It iterates the adapter registry, guarding each call so one
provider can't affect the caller or the others.
// services/analytics/track.ts
import type { AnalyticsEvent } from "@/constants/analytics";
import { analyticsAdapters } from "./adapters";
import { hasConsent } from "./consent";
export function track(
event: AnalyticsEvent,
props?: Record<string, unknown>,
): void {
if (!hasConsent()) return; // §6 — nothing fires before opt-in
for (const adapter of analyticsAdapters) {
try {
void Promise.resolve(adapter(event, props)).catch(() => {
// Drop provider failures; never recurse into analytics or log raw data.
});
} catch {
/* best-effort: a failing/absent provider must never throw into the
caller or block dispatch to the remaining providers. */
}
}
}Each adapter is a tiny (event, props) => void that guards its provider global — it no-ops on
the server (no window) and when the provider script is absent, so a missing or unloaded provider
never throws.
// services/analytics/adapters.ts
import type { AnalyticsEvent } from "@/constants/analytics";
// Supply AnalyticsGlobals type declarations and a platform-specific firebaseAdapter import.
export type AnalyticsAdapter = (
event: AnalyticsEvent,
props?: Record<string, unknown>,
) => void | Promise<void>;
export const googleAnalyticsAdapter: AnalyticsAdapter = (event, props) => {
const w =
typeof window !== "undefined" ? (window as AnalyticsGlobals) : undefined;
if (!w || typeof w.gtag !== "function") return; // SSR-safe + absent-safe
w.gtag("event", event, props ?? {});
};
export const clarityAdapter: AnalyticsAdapter = (event) => {
const w =
typeof window !== "undefined" ? (window as AnalyticsGlobals) : undefined;
if (!w || typeof w.clarity !== "function") return;
w.clarity("event", event);
};
// The registry track() fans out across. Exported + mutable so tests can swap
// in a recording sink to assert dispatch.
export const analyticsAdapters: AnalyticsAdapter[] = [
googleAnalyticsAdapter,
clarityAdapter,
firebaseAdapter,
// posthogAdapter, openPanelAdapter, …
];3.1 Firebase Analytics — one adapter, two platforms
Firebase Analytics ships two SDKs that share the same logEvent(name, params) contract, so a
single conceptual adapter covers both web and React Native — only the import and the "is it
available?" guard differ. On web the adapter never imports the SDK at module top level (it's
browser-only and async), so it stays SSR-safe.
// services/analytics/adapters.firebase.web.ts — Firebase JS SDK (web)
import type { Analytics } from "firebase/analytics";
import { hasConsent } from "./consent";
import type { AnalyticsAdapter } from "./adapters";
// Held after a lazy, browser-only init (below) so the adapter stays synchronous + SSR-safe.
let analytics: Analytics | undefined;
export function setFirebaseAnalytics(instance: Analytic… (nội dung đầy đủ có trong gói tải về)
Skill liên quan
- frontend-optimistic-mutations – Kỷ luật độc lập framework cho luồng ghi (write path) của mọi ứng dụng React hoặc React…
- frontend-seo – Hệ thống SEO độc lập framework, dùng được cho mọi frontend React hoặc React…
- frontend-ui-dark-ts – Hệ thống giao diện React theo chủ đề tối hiện đại, dùng Tailwind CSS và Framer Motion…
- frontend-ui-engineering – Xây dựng giao diện đạt chất lượng production. Dùng khi tạo hoặc chỉnh sửa giao diện…
- frutiger-aero – Hướng dẫn triển khai web và app theo phong cách Frutiger Aero. Kích hoạt khi người dùng…
- game-art – Nguyên tắc nghệ thuật game. Lựa chọn phong cách hình ảnh, quy trình tài nguyên, quy…
- game-audio – Nguyên tắc âm thanh game. Thiết kế âm thanh, tích hợp nhạc nền, hệ thống âm thanh thích…
- senior-computer-vision – Skill kỹ thuật thị giác máy tính cho phát hiện đối tượng, phân đoạn ảnh và hệ thống thị…