bondery-specific
Bondery-specific architectural decisions, product UX patterns, and their rationale.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Bondery-specific architectural decisions, product UX patterns, and their rationale.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | bondery-specific |
| description | Bondery-specific architectural decisions, product UX patterns, and their rationale. |
| metadata | {"version":"0.1.0"} |
Consult these resources as needed:
references/
api/ API design, usage, mutations, route ordering, sync (see api/README.md)
ux/ UX patterns — common/, mobile/, desktop/, product/ (see ux/README.md)
ux-patterns.md Redirect to ux/ (legacy path)
Monorepo shared packages follow a one-way dependency graph:
@bondery/schemas — contract layer (types, Zod validation schemas, constants). Must not import any other @bondery/* package.@bondery/helpers — behavior layer (parsing, formatting, geocoding, routes). May depend on schemas.@bondery/helpers/forms — Zod pipelines that validate with schemas then normalize with helpers. Use on form submit/save.| Need | Import from |
|---|---|
| Type or validation schema | @bondery/schemas |
| Utility / formatter | @bondery/helpers (subpath) |
| Form submit (validate + normalize) | @bondery/helpers/forms |
See packages/schemas/README.md and packages/helpers/README.md.
Shared packages follow the Turborepo compiled-package model:
@bondery/typescript-config (base.json or react-library.json); module / moduleResolution = NodeNext.types → src/, default → dist/; run npm run sync-exports after adding public subpaths.#* hash paths with .js suffix (not tsconfig paths).rimraf dist && tsc (+ rewrite hash imports); compile: incremental tsc for dev cold start; dev: tsc --watch run alongside apps via Turbo with.dist/ via exports — no transpilePackages, no packages/*/src aliases (mobile Metro resolves workspace packages from src/ separately).All Bondery clients call the Fastify API through a transport wrapper layer — never ad-hoc fetch with duplicated auth and error parsing. Read references/api/api-usage.md for:
*Json vs *JsonOrNull — throw on error vs graceful null@bondery/helpers/api — ApiError, nested error parsing, getUserFacingErrorreferences/api/api-usage.md § Unauthorized sessionsGET /api/status; chrome extension calls Fastify /status directlyPair with references/api/api-mutations.md when implementing create/update flows.
The webapp has a domain module layer on top of transport — same idea as mobile lib/domains/*, but backed by REST + TanStack Query instead of SQLite sync.
| Layer | Location | Use |
|---|---|---|
| Resources | lib/api/resources/* | Path builders, response normalizers |
| Domains | lib/api/domains/*, lib/api/domains/server/* | Typed API calls per feature |
| Query hooks | lib/query/hooks/* | Cache keys, mutations, invalidation |
Rule: Feature components and pages call domain hooks (useContactsQuery, mutation hooks, etc.) — not clientApiJson / serverApiJson directly. Transport stays in lib/api/client.ts and lib/api/server.ts.
See apps/webapp/src/lib/api/README.md and references/api/api-usage.md § Domain modules.
Tier-1 domain data (contacts, groups, tags, and child tables in SYNC_TABLE_KEYS) is local-first on mobile:
lib/sync/repositories/* + useSyncQuery — never REST list/detail in features/.submitSyncMutation via lib/domains/* — optimistic SQLite + unified outbox; drainer pushes immediately when online.PullManager bootstraps and long-polls GET /api/sync/pull; materializers apply server rows into SQLite (server wins on pull).lib/api/online-only.ts (settings, geocode, photos, share, vCard, account delete).Run npm run check-sync-patterns --workspace=mobile locally when touching mobile sync code. Full architecture: references/api/sync-architecture.md.
List endpoints follow a shared pagination contract. Read references/api/api-design.md for:
limit, offset, search, sort — no abbreviationspagination with totalCount, hasMore, and echoed sort/searchhasMore as the single source of truth for table “load more” UIPublished API reference order follows Fastify registration order. Read references/api/api-route-ordering.md when adding or reordering routes — path tiers, HTTP method order, sidebar tag order, and CI enforcement.
Do not hardcode user-facing strings in the webapp (or other clients that share packages/translations). All visible copy — labels, placeholders, buttons, notifications, aria labels, validation messages — belongs in translation files.
npm run i18n:status --workspace=@bondery/translations).en, cs, de — source of truth packages/schemas/locale/supported-locales.json. Import SUPPORTED_LOCALES / DEFAULT_LOCALE from @bondery/schemas/locale (also re-exported by @bondery/translations).packages/translations/src/locales/{en,cs,de}/** — one JSON file per namespace; add every new key to all supported locales.useWebTranslations(namespace, keyPrefix?), useCommonTranslations(), useValidationTranslations(keyPrefix?) from @/lib/i18n/useWebTranslations.GroupsPage, ContactInfo). Shared chrome lives in common.json (actions.cancel, feedback.errorTitle, etc.).useMobileTranslations(namespace?, keyPrefix?) or t("key", { ns: "MobileContacts" }) — never MobileApp.* dotted paths.useContactInfoLabels, useContactsTableCopy) over duplicating useMemo blocks. For modals opened outside React, set the title from the modal component via modals.updateModal once t is available.check-translations, check-api-error-translations, i18n:types:check, i18n:status:check, i18n:lint, verify-i18next-hook-extraction.mjs (see root package.json / .github/workflows/verify.yml).When touching UI, wire strings to translations in the same change — do not leave English literals for a follow-up.
Machine-readable API failures use a nested envelope: { "error": { "type", "code", "message", "request_id", "doc_url", ... } }.
@bondery/schemas/errors — API_ERROR_CODES, getErrorDefinition, getErrorDocUrl. Codes are snake_case only; no ad-hoc literals in production.apps/api/scripts/generate-api-error-catalog.ts, (2) docs page at /docs/api/errors/{code} on the website, (3) common.errors.api.{code} in en/cs/de.badRequest / notFound / internal / new DomainError(...) with catalog codes; global mapper in apps/api/src/lib/platform/errors/map-to-response.ts builds the nested body. Never put internal details in message for 5xx.@bondery/helpers/api — ApiError, buildApiErrorFromResponse, getUserFacingError(error, t). Show copy via getUserMessage(t) or getUserFacingError — never surface server message in notifications. App transport (clientApiJson, apiRequest) stays in each app.check-route-errors runs inside npm run check-types -w apps/api; also check-api-error-translations, check-error-docs, check-user-facing-errors at repo root.Do not use Mantine's Kbd from @mantine/core for shortcut hints in the UI. Use Kbd from @bondery/mantine-next — it wraps Mantine's chip with platform-aware labels via useOs (Ctrl vs ⌘, Shift vs ⇧, etc.).
import { Kbd, parseShortcutKeys } from "@bondery/mantine-next".<Kbd keys={["mod", "k"]} size="xs" /> — pass shortcut tokens, not pre-formatted strings. Use "mod" for the primary modifier (Ctrl on Windows/Linux, ⌘ on macOS/iOS).HOTKEYS: keys={parseShortcutKeys(HOTKEYS.COMMAND_PALETTE)} — keeps display in sync with useHotkeys / Spotlight bindings in @/lib/platform/config.Ctrl, Cmd, or ⌘ in JSX; do not translate shortcut labels via i18n (they are OS conventions, not locale strings).HOTKEYS + useHotkeys define behavior; Kbd + parseShortcutKeys define what the user sees. Keep both pointed at the same constant.Contact photos live in Supabase Storage at avatars/{userId}/{personId}.jpg. The people.has_avatar boolean (maintained by API write paths on upload/delete) gates whether the API returns a public URL in Contact.avatar or null.
resolveContactAvatarUrl() in the API checks has_avatar before constructing the storage URL. Clients should treat avatar: null as “show initials” — no phantom requests or 404 fallbacks.avatar-storage.ts helpers that update both storage and has_avatar together.PersonAvatar / ContactAvatar / Mantine Avatar with name + color for initials fallback when avatar is null.LinkedIn company/school logos use the separate linkedin_logos bucket and are unrelated to has_avatar.
Use Fastify built in console logging functions of request.log and reply.log instead of console.log for better performance, structured logging, and integration with Fastify's logging ecosystem.
Route schemas: Use Zod from @bondery/schemas and @bondery/schemas/http with fastify-zod-openapi — not TypeBox. Export route plugins as AppRoutePlugin from apps/api/src/lib/platform/fastify-types.ts. Put satisfies FastifyZodOpenApiSchema on the inner schema object (not the route options wrapper). Do not annotate handlers with reply: FastifyReply — it breaks request type inference. In onRoute hooks, mutate routeOptions.schema.tags in place; never { ...routeOptions.schema } (spread drops the plugin symbol config and breaks OpenAPI generation). Call applyOpenApiRouteMeta(routeOptions, { area }) from apps/api/src/lib/platform/openapi/meta.ts on every top-level route plugin (integration = API key + bearer, session = bearer only, internal = hidden from public docs). Every route schema must include description and response — use withOkResponse / withCreatedResponse from apps/api/src/lib/platform/openapi/responses.ts for standard success + error shapes. Shared read models are registered for OpenAPI $refs via registerOpenApiComponentSchemas() at API bootstrap. Registration order is published doc order — follow references/api/api-route-ordering.md when adding routes. Regenerate apps/api/openapi.yaml after route changes; see docs/contributing/api-routes.md.
When reviewing code, focus on the following aspects:
references/ux/README.md — start with common/ (empty states, loading, lists, writing), then mobile/, desktop/, or product/ as needed.Always install Postgres extensions in the extensions schema, never in public. Installing extensions in public is a security risk (Supabase advisor lint 0014_extension_in_public) because public users can exploit extension objects to escalate privileges.
Rule: Every CREATE EXTENSION statement in a migration must include WITH SCHEMA extensions.
-- ✅ correct
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA extensions;
CREATE EXTENSION IF NOT EXISTS unaccent WITH SCHEMA extensions;
-- ❌ wrong – omitting WITH SCHEMA defaults to public
CREATE EXTENSION IF NOT EXISTS pg_trgm;
When an extension lives in extensions, all references to its functions and operators must be schema-qualified as extensions.<function> (e.g., extensions.unaccent(...), extensions.gin_trgm_ops, extensions.word_similarity(...)).
Legacy migrations: pg_trgm / unaccent were once created in public (20260404100000) and moved to extensions in 20260408100000_move_extensions_to_extensions_schema.sql. uuid-ossp remains in public from the initial schema and is unused (tables use gen_random_uuid()). New installs should not repeat CREATE EXTENSION in public; remediate with ALTER EXTENSION … SET SCHEMA extensions or drop unused extensions.
is or has to indicate their true/false nature (e.g., isActive, hasPermission).get (e.g., getUser, getContactList).set (e.g., setUser, setContactList).is (e.g., isUserActive, isContactVerified).has (e.g., hasPermission, hasAccess).Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, and Expo Router data loaders (`useLoaderData`).
Interact with Plane.so project management via MCP. Use when: creating work items, updating issues, listing tasks, searching Plane, managing cycles/sprints, organizing modules, tracking epics, assigning issues, adding comments, creating labels, managing states, viewing project boards, or any task involving Plane work items, PROJ-123 identifiers, or project planning in Plane.
A collection of guides, best practices, and resources for using Mantine UI in your projects.
Postgres performance optimization and best practices from Supabase. Use this skill when writing, reviewing, or optimizing Postgres queries, schema designs, or database configurations.
When the user wants to audit, review, or diagnose SEO issues on their site. Also use when the user mentions "SEO audit," "technical SEO," "why am I not ranking," "SEO issues," "on-page SEO," "meta tags review," or "SEO health check." For building pages at scale to target keywords, see programmatic-seo. For adding structured data, see schema-markup.