ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A comprehensive historian application for managing persons, events, and literature with bibliography synchronization
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A research environment for historians: a workspace for recording people, events, and archival sources, the relationships between them, and — critically — the evidence and degree of confidence behind every claim. The domain is deliberately complex; the UI's job is to keep that complexity away from the user. Target: university MVP validation, then commercialization. [README 설치·사용 부분 발췌] Getting startedREADME 전문 보기 ↗
A research environment for historians: a workspace for recording people, events, and archival sources, the relationships between them, and — critically — how firmly their dates, places and relations are held, and the evidence a researcher attaches to them.
The domain is deliberately complex; the UI's job is to keep that complexity away from the user. Target: university MVP validation, then commercialization.
| Layer | Choice | Notes |
|---|---|---|
| Framework | Next.js 15 (App Router) + React 19 | Server Components by default, client leaves only where interactivity needs it |
| Language | TypeScript 5.8, strict | Plus noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride |
| Package manager | pnpm 9.15 (Node ≥ 22) | Always pnpm — never npm or yarn |
| Styling | Tailwind CSS v4 | CSS-first. No tailwind.config.js — design tokens live in src/styles/globals.css under @theme |
| Components | shadcn/ui on Radix primitives | Local, editable source in src/components/ui/ |
| Database | PostgreSQL (Neon, serverless) | Pooled + direct (unpooled) endpoints; migrations require the direct one |
| ORM | Prisma 6 | migrate dev locally, migrate deploy in CI — never db push |
| Auth | Auth.js (next-auth) v5, Credentials + JWT | Split config: auth.config.ts is Edge-safe for middleware, auth.ts carries Prisma + bcrypt |
| Cache / limiting | Upstash Redis (Vercel Marketplace) | No in-memory state — the app scales horizontally |
| Validation | Zod 3 | Shared schemas in src/lib/schemas/ — one source of truth per entity |
| Forms | react-hook-form + @hookform/resolvers | |
| i18n | next-intl 3 | de (default) and en, always locale-prefixed; localeDetection: false |
| Resend | Verification and password-reset flows | |
| Content | MDX via next-mdx-remote | Changelog entries only, from content/changelog/{version}.{locale}.mdx — repo-authored, never user input |
| Testing | Vitest + Testing Library; Playwright | Unit + E2E across Chromium and Firefox |
| Hosting / CI | Vercel; GitHub Actions | Lint → typecheck → unit → build → E2E → deploy (not live) → promote → assert live commit |
pnpm install
cp .env.example .env.local # then fill in the values (see src/lib/env.ts for what is required)
pnpm prisma migrate dev
pnpm prisma db seed
pnpm dev
| Command | Purpose |
|---|---|
pnpm dev | Dev server (Turbopack) |
pnpm test | Unit tests |
pnpm test:e2e | Playwright E2E |
pnpm typecheck | tsc --noEmit |
pnpm lint | ESLint |
Prisma CLI reads
.env, not.env.local. Pass the vars explicitly when needed:DATABASE_URL="…" DATABASE_URL_UNPOOLED="…" pnpm prisma migrate dev
pnpm test:e2e starts and owns its own app server (pnpm dev locally and
pnpm start after CI builds). It always uses the local-only stub email
transport with placeholder Resend values, binds Auth.js to
http://localhost:3000, and passes dedicated cache and rate-limit namespaces.
It refuses to reuse an already-running server so that its database and email
configuration cannot be mistaken for the test configuration. EMAIL_TRANSPORT
defaults to resend; use stub only for this local E2E workflow.
CACHE_NAMESPACE and RATELIMIT_NAMESPACE are required outside
production (pnpm dev, tests, CI) — set both to the same value, e.g.
local-dev. Local development and production share one Upstash instance, and
without these the app writes cache entries and rate-limit buckets into
production's bare key space (#124); src/lib/cache.ts and
src/lib/rate-limit-key.ts throw when either is unset outside production
rather than doing that silently. Production itself must stay un-namespaced —
that is its own key space.
PURGE_SECRET is required in production (a Vercel deployment with
VERCEL_ENV=production; at least 32 characters, checked in src/lib/env.ts)
and optional elsewhere. It is the bearer secret for
POST /api/internal/purge-access-requests, which deletes expired access
requests and invites. The same value must also be a GitHub Actions secret
named PURGE_SECRET: the hourly workflow
.github/workflows/purge-access-requests.yml reads it to call that endpoint.
Vercel binds env at deploy time, so redeploy after setting it.
Most database schemas assume the world is knowable: a person has a birth date, a fact is either true or absent. Historical research does not work that way. The evidence is fragmentary, contradictory, and always mediated by a source. A schema that cannot express "probably March 1848, according to one parish register, though a letter suggests otherwise" forces the researcher to either fabricate precision or discard information.
Every decision below follows from that single constraint.
Dates are not DATE columns. Each is a nullable year / month / day triple:
birth_year Int?
birth_month Int?
birth_day Int?
birth_date_certainty Certainty @default(UNKNOWN)
A DATE column cannot represent "1769" or "March 1848" without inventing the missing
components — and an invented 1769-01-01 is indistinguishable from a real one once written.
Separate fields let the record state exactly what the source states, no more.
The ordering rules (a month requires a year; a day requires a month) are enforced in the shared Zod schema rather than the type system, since they are a semantic constraint, not a structural one.
enum Certainty { CERTAIN PROBABLE POSSIBLE UNKNOWN }
Four deliberate choices:
Location records and Source records carry none (a Source has reliability, a
judgement about the document rather than a belief about the past).UNKNOWN on write.
Without that rule the database accumulates confidence attached to an empty field: the UI warns
that a claim nobody made lacks evidence, and a value entered later silently inherits a
confidence asserted about something else.This is the core of the model. Two tables attach sources to claims:
RelationEvidence — cites a source for a relationship.PropertyEvidence — cites a source for one specific field on any entity.Both carry page_reference ("S. 47", "fol. 12r", "col. 3"), a quote, and their own
confidence. PropertyEvidence additionally separates raw_transcription (verbatim, diplomatic)
from quote (normalized/interpreted) — the distinction between what the document literally says
and what the researcher reads it as saying.
The key design point: evidence supplements the entity, it does not replace it. The entity
holds the researcher's current best estimate; PropertyEvidence records what each source claims
about that property. When two sources disagree, both records persist and the UI can surface the
conflict rather than silently resolving it. Scholarly work is reproducible only if every claim
traces back to what supports it.
Genealogy tools ship a closed set of relationships (parent, spouse, sibling). Historical research needs "was patron of", "corresponded with", "was imprisoned at", "cites". So relations are polymorphic and their taxonomy is user-defined per project:
from_type EntityType // PERSON | EVENT | SOURCE | LOCATION | LITERATURE
from_id String // NOT a foreign key — see below
to_type EntityType
to_id String
RelationType declares which entity types are legal on each side (valid_from_types,
valid_to_types), so the vocabulary is extensible without schema migrations.
The trade-off is explicit and documented in the schema: because from_id may reference any
of five tables, no SQL foreign key can be declared. Referential integrity for polymorphic columns
is enforced at the application layer only. This is a real cost, accepted knowingly in exchange
for an open relationship vocabulary. Composite indexes on (from_type, from_id) and
(to_type, to_id) keep graph traversal efficient.
A relationship is rarely timeless. "Married 1848–1867" is a different fact from "married":
valid_from_year Int?
valid_from_month Int?
valid_from_cert Certainty
valid_to_year Int?
valid_to_month Int?
valid_to_cert Certainty
The same partial-date-plus-certainty pattern as entity dates, applied to the edge itself. This also means two otherwise-identical relations may legitimately coexist with different validity periods.
Source — primary evidence: an archival document, letter, newspaper, photograph. Carries
repository, call_number, and a SourceReliability rating, because locating and appraising
the artefact is part of the work.Literature — secondary scholarly references, with bibliographic fields.Collapsing them into one "reference" table would erase the primary/secondary distinction that
historical method rests on. Only Source can serve as evidence.
Historical figures appear under varying spellings, languages, Latinized forms, titles, and
married names. PersonName stores every variant with an optional ISO 639-1 language and an
is_primary display flag. A single name column would silently privilege one form and make the
others unsearchable.
Research is iterative and revisable, so Person, Event, Source, and Relation carry
deleted_at rather than being destroyed. EntityActivity is an append-only audit log of every
create/update/delete — deliberately never given a DELETE endpoint, so the provenance of the
research record itself is preserved.
Location and Literature are treated as reference data and currently have no soft delete
(see Known limitations).
EntityActivity is append-only because of a constraint the model is already built to
satisfy: an agentic layer is committed roadmap scope (docs/strategy/roadmap.md, Phase 6),
and its design requires agents to hold no write authority of their own. An agent will only
ever be able to submit a suggestion, never write directly; every suggestion must be grounded
in an existing Source record; and turning a suggestion into a change requires an explicit
human ACCEPT. For that guarantee to be checkable, the log of what actually changed the
research record can never be edited or deleted after the fact — including by an agent acting
on an accepted suggestion. prisma/schema.prisma (around line 542) documents the no-DELETE
constraint on this table and cites the roadmap as its authority. None of this is built
today: created_via and AgentSuggestion do not exist yet in the schema or the
application code; EntityActivity.agent_name and .source_id are reserved and always
null today — no caller supplies them until Phase 6 is implemented.
Every user-data table carries project_id, and access runs through UserProject with
OWNER / EDITOR / VIEWER roles. Retrofitting tenancy is notoriously error-prone; it was
designed in from the first migration.
Because every write needs a project, one is provisioned on first sign-in and the user is made
its OWNER (src/lib/project.ts). The active project is then the user's first OWNER/EDITOR
membership, carried in the session — a stopgap until the project switcher replaces it, and the
reason there is no "no project" state to design around.
CIDOC CRM (ISO 21127) is the reference ontology for cultural
heritage information. Evidoxa is deliberately CRM-informed but not CRM-compliant. This was
an explicit, documented decision, not an oversight — four candidate architectures were evaluated
in docs/specs/1-2-database-schema-data-layer/architecture-evaluation.md,
including a claim-centric model noted there as "academically correct (aligns with CIDOC CRM,
ResearchSpace)" and a full bitemporal claim graph described as "the correct long-term target for
a Phase 5+ academic edition."
E13 Attribute Assignment.| CRM approach | Evidoxa |
|---|---|
| Everything is an event; properties are reified assignments | Entities hold properties directly as columns; evidence supplements them |
Formal class/property URIs (E21 Person, P4 has time-span) | Application-local table and column names |
| Global, resolvable URI identity for every resource | Project-scoped CUIDs |
| Bitemporal: transaction-time and valid-time both modelled | Valid-time on relations only; no transaction-time dimension |
| Contradictory claims coexist as separate assignments | One best-estimate value per field, with PropertyEvidence recording what each source says |
The last row is the substantive gap. In CRM, "Source A says 1848, Source B says 1851" is two
equally-weighted assignments with no privileged answer. In Evidoxa the entity carries the
researcher's current best estimate and PropertyEvidence records the competing testimony
alongside it. The information is preserved and conflicts can be surfaced — but the schema still
privileges one value.
Pros of the current position
String, which discards the
schema contract at exactly the layer that makes the TypeScript strictness worth having.Cons — what it genuinely costs
Compliance is best treated as export-first, then internal — the ordering matters, because a mapping layer delivers most of the interoperability benefit at a fraction of the cost.
Person → E21, Event → E5, Location → E53, Source → E31, RelationType → P terms.
No schema change; a serialization layer only. This alone buys most interoperability.PropertyEvidence to a full claim layer. It already carries entity_type,
entity_id, property, source_id, confidence, quote, and raw_transcription — it is
deliberately one value column and a superseded_by_id away from Option B's Claim table.
Entity columns then become a materialized "preferred reading" derived from claims rather than
the source of truth.Steps 1–2 are low-cost and worth doing regardless. Step 3 is the real paradigm shift and should be driven by evidence that researchers actually need multi-claim workflows — the evaluation deliberately deferred it rather than imposing it on users from day one.
The domain is a graph, so the question is fair. The model is a property graph in all but storage:
polymorphic edges, composite indexes on (from_type, from_id) and (to_type, to_id), and a
user-defined edge taxonomy.
Why relational won
WHERE project_id = ? AND deleted_at IS NULL ORDER BY last_name — which
is precisely what a relational planner with B-tree indexes is built for. Graph databases pay
for traversal power with weaker performance on this bread-and-butter case.pgvector or
pg_trgm for search without changing stores.What it costs
EntityType enum migration plus a table;
in a schema-optional graph store it is just a new label.When to revisit
If network analysis becomes a headline feature — centrality, clustering, "how are these two figures connected", six-degrees exploration over large corpora — the sound move is a read-only graph projection (Neo4j, Memgraph, or Apache AGE in-place on Postgres) fed from the relational store, rather than migrating the system of record. Postgres stays authoritative for transactions, tenancy, and auth; the graph store serves the traversal-heavy queries it is good at. Committing to that split before the analytical requirements are concrete would be premature.
Documented honestly, since these are live constraints rather than oversights:
findMany and
findFirst; count, update, updateMany, delete, and groupBy bypass it and must filter
explicitly.Location and Literature lack soft delete despite being full members of the relation
graph — deleting one permanently orphans relations that reference it. To be addressed before
their CRUD is built.Person years are bounded 1–2100 while event and relation years
are unbounded — an inconsistency to resolve before the model claims pre-Common-Era coverage.A full architectural review lives in docs/archive/project-review-2026-07-13.md;
specs are under docs/specs/.