Skip to content

Logo integration#6

Merged
ndemarco merged 15 commits intomainfrom
logo-integration
Apr 19, 2026
Merged

Logo integration#6
ndemarco merged 15 commits intomainfrom
logo-integration

Conversation

@ndemarco
Copy link
Copy Markdown
Owner

No description provided.

NickyDoes added 15 commits April 17, 2026 23:09
Restore the WhereTF map pin logo to the sidebar header, add it as
an SVG favicon (both app/icon.svg route and public/ static fallback),
and replace all plain "Loading..." text with an animated pin spinner.

Remove unused default Next.js boilerplate SVGs from public/.
Fix stale .env.local.example (was referencing MongoDB/OAuth).
Spec covers admin CRUD, template+receptacle integration, placement
check (interface match + dimensional fit), lifecycle (archive/merge/
delete), maturity states, and optional unit-system convenience layer.

Prototype shows list view with filter tabs, bulk merge flow, detail
panel with per-axis unit editor, archive/delete lifecycle menu, and
chip preview.
Maturity radio in the edit form was too prominent. Remove it entirely.
Default new interfaces to stable; "Save as draft" secondary button is
the explicit opt-in to draft. Edit form for stable types has no
demotion path — state machine is one-way (draft → stable → archived).

Detail header shows a small draft pill when viewing a draft.
Slice 1 of interface-type-management implementation — data layer only,
no UI yet. Extends interface_types with maturity (draft|stable),
archived_at timestamp, and unit_system jsonb. Adds junction tables for
template_version × interface_type (provided + accepted) and
location × interface_type (accepted). Old single-text columns stay in
place and authoritative for now; slice 2 migrates template/location
repos onto the junctions.

Repository gains archive / unarchive / usageCount. Update enforces
one-way maturity state machine — stable is terminal. Remove requires
archived + zero usage. List supports status=active|archived|all.

API: new POST /api/interface-types/:id/archive and /unarchive routes.
Existing PATCH returns 409 on stable→draft demotion. DELETE returns
409 until archived and unused. GET detail includes usage counts.

40 repo tests pass; end-to-end smoke test via curl covers the full
lifecycle.
Slice 2 of interface-type-management. Single-text columns on template
versions, locations, and inserts are replaced by UUID-keyed junction
tables. Backfill resolves identifiers to UUIDs in migration 0014 then
drops the old columns. template_versions.unit_size (the "42mm" text
field) also drops — unit info now lives on interface_types.unit_system.

Repositories now take UUID arrays for interface membership:
  templateRepository.create / publishVersion accept
    interfacesProvidedIds[] and interfacesAcceptedIds[]
  locationRepository.create accepts interfacesAcceptedIds[]
  insertRepository drops the interfaceTypeProvided param — inserts
    inherit provided interfaces from their template version per spec
    (no per-insert override)

New helpers: templateRepository.getVersionInterfaces /
setVersionInterfaces, locationRepository.getAcceptedInterfaces /
setAcceptedInterfaces / getAcceptedInterfacesByLocationIds.

Placement check is now set intersection over UUIDs. Still optimistic
when either side is empty (user rules the storage).

API: /api/locations GETs now include interfacesAccepted on each
location; PATCH accepts interfacesAcceptedIds to drive
setAcceptedInterfaces. /api/inserts list filter renamed to
interfaceTypeId (UUID) and listWithDetails returns
interfaceTypes: [{ id, identifier }].

Seed rewritten to use repo helpers and pass UUIDs. Full test suite
passes (397 tests).

Pages (app/inserts, app/modules) still reference the old single-string
shape and will get stale data until slice 4 rebuilds the chip UI.
Slice 4 of interface-type-management. New route /admin/interfaces is
the admin surface for managing interface types. List view on the left
(checkbox + identifier + maturity badge + status badge + description +
usage counts + created date + row actions) with Active / Archived /
All filter tabs and a bulk-actions bar. Detail pane on the right opens
on row click or the "New Interface Type" header button.

Detail form mirrors the prototype: identifier (mutable slug), multi-
line description, unit-system toggle with per-axis label + mm input,
free-form physical-contract notes, live usage panel. Derive-new button
clones description + contract + unit system into a fresh draft.

Save button behavior follows the spec state machine:
  Create mode: "Save as draft" + "Create" (stable default)
  Edit draft: "Save as draft" + "Save" (promotes to stable)
  Edit stable: single "Save" — no demotion path

Lifecycle menu in the header handles Archive / Unarchive / Delete.
Delete is gated on archived + 0 usage; the menu surfaces the reason
when disabled. Archive row action mirrors the same logic in-line.

Sidebar gains an Interfaces entry under the Admin section so the
route is discoverable. Merge (bulk action) is stubbed with a
disabled button — lands in the final slice alongside its backend.
Final slice of interface-type-management. The merge operation
consolidates N source interface types into a single target, in one
transaction:

  1. Rewrite template_version_interfaces_{provided,accepted} junctions
     from source IDs to target ID, deduping on conflict so a template
     version that already holds target doesn't get a duplicate row.
  2. Rewrite location_interfaces_accepted the same way.
  3. Mint a new template version per affected template, cloning the
     latest affected version's structure and carrying the now-remapped
     junctions forward — interfaces-provided/accepted are versioned
     properties per spec, so a merge must be a versioned event.
  4. Hard-delete the source interface_types rows (merge bypasses the
     archive-gate; tombstoning is part of the consolidation).

Errors: empty sourceIds (400), target in sources (409), target or
source not found (404).

API: POST /api/interface-types/merge with { sourceIds, targetId }
returns { referencesUpdated, templateVersionsMinted, sourcesDeleted }.

Admin page: "Merge into…" bulk action opens a modal showing the
selected sources, total reference count, and a radio-pick survivor
list (active non-selected types, with usage ref counts). Destructive
warning surfaces the ref + version mint counts. Execute refreshes the
list, closes the detail pane if a source was active, and toasts the
summary.

Interface-type-management work is now complete (slices 1–4).
50 repo tests pass; smoke-tested end-to-end via curl.
Slice 2 dropped the single-text interface_type columns and moved
membership to junction tables, but the /inserts, /modules, and
/modules/new pages were still reading ins.interfaceType (string) and
level.interfaceTypeAccepted (string) and sending those names back in
mutation bodies. The fields no longer exist, so data was stale and
mutations silently dropped.

All four pages now use the new shape:
  - Location.interfacesAccepted: Array<{ id, identifier }>
  - Insert.interfaceTypes: Array<{ id, identifier }>
  - Level form state switches from interfaceTypeAccepted: string to
    interfaceTypeId: string (single-select UUID; multi-select chips
    are a later slice)
  - Query params and PATCH/POST bodies use interfaceTypeId /
    interfacesAcceptedIds (UUIDs), never identifier strings
  - Dropdowns key by UUID id, display identifier
  - Chip rendering joins identifiers when a level accepts or an
    insert provides multiple

templateRepository.listWithCurrentVersion now batch-loads provided
and accepted interface identifiers onto currentVersionData so the
module page can filter candidate templates against a receptacle's
accepted interface without a second round-trip.

No schema change. All 407 tests pass. Smoke-tested /modules,
/modules/new, /modules/<id>, /inserts, /admin/interfaces — all return
200 and the API responses carry the new array shape.
TS2322: setDraft is SetStateAction<FormDraft | null>, but the
DetailForm prop declared the narrower (FormDraft | (FormDraft) =>
FormDraft) => void, which an updater that returns null couldn't
satisfy. Use React's SetStateAction<FormDraft | null> directly so
the prop signature matches the useState setter exactly.
auth-roadmap.md drops IdP as a WhereTF phase (homelab team owns it)
and becomes the 5-phase index for app-side work. deployment.md
§Authorization replaces the 2-mode per-org/global split with the
isolated/additive/open model and spells out every table's mode.
project-intent.md replaces "future: private items" with the concrete
ownerOrgId nullable mechanic.
Foundation for app-side multi-tenancy. Auth.js v5 with hybrid
providers (email+password credentials, optional homelab OIDC, dev
impersonate). New users/orgs/user_orgs tables via migration 0015.
Every additive + isolated data table gains ownerOrgId (nullable, FK
to orgs); migration 0016 seeds a default org and backfills isolated
rows. NOT NULL flip on isolated tables is deferred to 0017 so the
repo layer can migrate first.

Session strategy is JWT (Auth.js v5 constraint when Credentials is a
provider). Per-request authz hydrates via getRequestContext() —
reads orgs/role from DB, falls back to the wtf-active-org cookie.
Scope helpers additiveOrgFilter/isolatedOrgFilter build the two
filter shapes used by every repo.
Every repository method now takes { userId, orgId, ... } for writes
and { orgId, ... } for reads. Isolated tables filter strictly;
additive tables union global ∪ org via additiveOrgFilter. Writes
default org-private; catalog contributions pass asGlobal: true.
transactionRepository.log carries actor + org on every audit row.

API routes call requireContext() and forward ctx to repos — no route
trusts its own URL for tenancy. The shared requireContext /
errorResponse helpers keep per-route boilerplate tight.

Tests updated to seed a fresh org per beforeEach (testCtx) and
spread it into every repo call. Two-org isolation test per repo
(12 new files) asserts cross-org invisibility on the isolated side
and proper global+own union on the additive side.

seed.ts now looks up the default org/user from the migration seed
and writes catalog rows (items, templates, taxonomy, interface
types) with asGlobal: true so they remain shared across orgs.
Every isolated-mode repo now populates owner_org_id on write (Phase
C.2 landed), so the nullable window from migration 0016 is safe to
close. 0017 backfills any stragglers to the default org then applies
SET NOT NULL on the 6 isolated tables (modules, inserts, locations,
assignments, location_interfaces_accepted, transactions).

Schema files mirror the constraint with .notNull(). transactionRepository.log
tightens userId + orgId back to required — they were loose during
the C.2 rollout. Small merge() fix in interfaceTypeRepository uses
the active orgId directly when rewriting location junctions rather
than preserving a per-holder owner that can no longer be null.

432/432 repo tests still pass.
@ndemarco ndemarco merged commit 82c853e into main Apr 19, 2026
4 checks passed
@ndemarco ndemarco deleted the logo-integration branch April 19, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant