# ID Strategy

This is a Post.Build.Ship reference, not a standalone skill.

Use this reference when `post`, `build`, or `ship` is already active and the work introduces or
changes how an entity's ID is generated, stored, or surfaced in URLs, routes, or deep links.

This reference supplements Post.Build.Ship. It preserves Authorizing User authority, observable ID
contracts, material boundaries, fail-fast approvals, proportionate QA, ownership, and Ship safety.
It adds no mandatory review or presentation fields.

## Decision Rule

Default to `nanoid` for application-generated IDs.

Use this decision rule:

1. Default: `nanoid`
2. Exception: UUIDv7 when ordered IDs are needed
3. Consider `bigint` only for purely internal database keys where DB-assigned numeric IDs are clearly
   the best fit
4. Do not introduce UUIDv4 as a casual default

If proposing a non-`nanoid` ID for a new feature, explain why the table or entity needs ordered IDs,
database-native IDs, or a separate internal/public ID strategy.

## Why `nanoid` Is The Default

This workspace is cross-platform and may use entity IDs in web URLs, mobile routes, desktop
navigation, deep links, and copy/pasteable identifiers.

For those use cases, `nanoid` is a strong default because it is URL-safe, short, random, easy to
generate on the client or server, and convenient to reuse across web, mobile, and desktop surfaces.
It gives the workspace one consistent default for IDs that may become externally visible or
route-safe.

## Why Not UUID By Default

UUID introduces an additional design choice on every use: unordered UUIDv4 versus time-ordered
UUIDv7. UUIDv4 is less attractive for write-heavy tables because it is random and not time-ordered.
UUIDs are also longer and less ergonomic in URLs, routes, logs, and cross-platform navigation.

UUIDv7 is still appropriate when ordered IDs are specifically needed, such as write-heavy
append-oriented tables where insertion locality matters, IDs that benefit from time ordering, or
distributed ID generation where ordering is useful.

If ordered IDs are not needed and the ID may appear in URLs or routes, prefer `nanoid`.

## Why Not `bigint` By Default

Sequential integers are useful for pure server-side database work, but they require coordination with
the server. That breaks when a client needs to create a record before the server sees it.

Offline-first and local-first architectures require clients to generate globally unique IDs
independently. A database-assigned `bigint` cannot satisfy that requirement for client-created
records.

Use `bigint` only for purely internal database keys where DB-assigned numeric IDs are clearly the
best fit and client-side creation, URLs, routes, deep links, and copy/pasteable identifiers are not
part of the ID's job.

## Local-First Rationale

In local-first and offline-first architectures, clients must assign IDs before syncing with the
server. The canonical local-first pattern used in Rocicorp's Replicache examples is `nanoid()` for
client-generated IDs because it is simple, short, and globally unique without coordination.

```ts
await rep.mutate.createTodo({ id: nanoid(), text: 'take out the trash' });
```

Treat that as evidence for the default, not as a requirement to use Replicache or to copy example
entity names.

## Tradeoffs

`nanoid` is shorter than UUID, URL-safe by default, good for routes and deep links, works well across
web, mobile, and desktop, and is easy to generate without server coordination.

Its tradeoffs are that it is random, not time-ordered, less ideal for append-heavy database primary
keys than ordered identifiers, requires a package rather than only built-in platform APIs, and is not
a native Postgres type.

When those tradeoffs matter, consider UUIDv7 for ordered distributed IDs or `bigint` for purely
internal database-assigned keys. Do not use UUIDv4 as the casual compromise.
