stash
This commit is contained in:
parent
9c9f0f2d9b
commit
6a9ed35bcd
11 changed files with 166 additions and 186 deletions
|
|
@ -102,11 +102,14 @@ A template's `layout` is a tree as described in label.js, with leaves whose `typ
|
|||
### Generated QR-Only Template Matrix
|
||||
`QR_ONLY_TEMPLATES` is the full "just the code" matrix: every `{symbology, error-correction level, [rMQR] size strategy}` combination label.js's `QR_LEAF_TYPES` supports, one template each. A plain `id` (no suffix) is always anyd's own defaults: ecc `"M"` and, for rMQR, size `"balanced"`. Coverage isn't uniform (see `QR_LEAF_TYPES`): full QR gets all four ecc grades L/M/Q/H; Micro QR swaps `"L"` (QR's actual lowest) for the even-lower, M1-only, detection-only `"Detection"`, and has no `"H"` at all; rMQR only ever supports ecc `"M"` or `"H"`, each crossed with all three size strategies (balanced/min/max). The matrix is generated (rather than hand-writing every near-duplicate entry) so that a symbology/level/size combination it's missing is one new row here, not a new block to keep in sync with its neighbors. `id` doubles as the layout's leaf `type`, since that's exactly what `QR_LEAF_TYPES` is keyed by.
|
||||
|
||||
### Derived Vars Shape And Ordering
|
||||
A derived var (`DERIVED_VARS`) is a format string calculated from other vars rather than typed directly - it doesn't get its own input, just a read-only, live-recalculated display next to the ones that do (see Print.vue and `withDerivedVars`). Its `inputs` name every var (base or, in principle, another derived one - e.g. `itemUrl`/`itemHandle`, which both read the derived `userHandle`) that `calc` reads. `inputs` is declared up front rather than inferred from `calc`'s body so `BASE_VARS` can include a var like `"webdomain"` that only feeds a calculation and that no template ever references directly. Declaration order matters here: `withDerivedVars` runs these in a single pass, so a derived var must be declared after every other derived var it depends on.
|
||||
### Content Kinds Registry
|
||||
`CONTENT_KINDS`/`CONTENT_KINDS_BY_ID` (`label-layouts.js`) is the single registry for everything that varies per content kind (currently `item`/`storage-location`): the handles-and-shortids.md kind letter (`typedPrefix`), the short-id.js schema name to use for an individually- vs. group-owned thing of that kind (`shortIdKind`/`groupShortIdKind`), the field name short-id.js's `serializeShortId` expects for its local id (`localIdField`), and a builder for the kind's long-form URL (`buildUrl`, `undefined` for a future kind added with no such route - see the `url` `DERIVED_VARS` entry). Every place that used to special-case "item vs. storage location" - `label.js`'s prefill builders, `label-layouts.js`'s `DERIVED_VARS`, Print.vue's `shortId()` - reads this one table instead, keyed by the Content card's `kind` field (a `<select>`, see Print.vue) rather than inferring which kind is meant from which id-shaped field happens to be populated.
|
||||
|
||||
### Location Id Known Var
|
||||
`BASE_VARS` is computed purely from `KNOWN_VARS` (every template's `required_vars`, see `templateIsAvailable`) plus whatever `DERIVED_VARS` reads - there's no separate registry of "fields a prefill might supply". `label.js`'s `"storage-location"` `LABEL_FIELD_BUILDERS` entry has always produced a `locationId`, and Print.vue's `shortId()` method has always checked `f.locationId` to resolve a `storage_location` short link, but until the `"location-id"`/`"owner-id-text-location"` templates existed, no template's `required_vars` ever named `locationId` - so it was never in `BASE_VARS`, Print.vue's `fields()` computed (which only copies `BASE_VARS` keys out of `varValues`) silently dropped it, and `shortId()` could never see it. That meant `shortId`/`shortUrl` never resolved for a storage location, so none of the short-link templates - including `"short-url-qr"`, whose own description says it's "for this item or storage location" - were ever actually available for one, despite the plumbing existing end to end. Any future prefill field meant to reach `fields()`/`DERIVED_VARS` needs the same anchor: at least one template naming it in `required_vars`.
|
||||
### Derived Vars Shape And Ordering
|
||||
A derived var (`DERIVED_VARS`) is a format string calculated from other vars rather than typed directly - it doesn't get its own input, just a read-only, live-recalculated display next to the ones that do (see Print.vue and `withDerivedVars`). Its `inputs` name every var (base or, in principle, another derived one - e.g. `qualifiedHandle`/`url`, which both read the derived `userHandle`, and `qualifiedHandle` also reads the derived `typedPrefix`) that `calc` reads. `inputs` is declared up front rather than inferred from `calc`'s body so `BASE_VARS` can include a var like `"webdomain"` that only feeds a calculation and that no template ever references directly. Declaration order matters here: `withDerivedVars` runs these in a single pass, so a derived var must be declared after every other derived var it depends on - `typedPrefix` before `qualifiedHandle`, in particular.
|
||||
|
||||
### Kind And Id Known Vars
|
||||
`BASE_VARS` is computed purely from `KNOWN_VARS` (every template's `required_vars`, see `templateIsAvailable`) plus whatever `DERIVED_VARS` reads - there's no separate registry of "fields a prefill might supply". `kind` and `id` reach `BASE_VARS` (and so the Content card's form, and `fields()`) the same way any other var does: named directly in several templates' `required_vars`, and `kind` also as an input of the derived `typedPrefix`. A content var meant to be prefillable/typeable needs that same anchor - at least one template naming it in `required_vars`, or some `DERIVED_VARS` entry reading it - or `fields()` (which only copies `BASE_VARS` keys out of `varValues`) silently drops it.
|
||||
|
||||
## Print View
|
||||
|
||||
|
|
@ -114,7 +117,9 @@ A derived var (`DERIVED_VARS`) is a format string calculated from other vars rat
|
|||
The tape-fed preview draws two representations of the tape: `.tape-full` (in the template, wrapping `.label-preview`/the canvas) spans the tape's entire physical width (`tape.mediaWidthMm`), while the canvas inside it is only `printAreaPx` wide. A label printer's print head can't mark all the way to a tape's outer edges, so the true printable area is narrower than the tape itself, by an amount that isn't a fixed or predictable fraction of the tape width. `.tape-full` centers the canvas within itself (equal margin on both sides) and applies a faint background tint to that margin, so it reads as real, if unprintable, tape rather than empty page space. Because of this gap, the vertical ruler (`verticalRulerTicks`) is deliberately built from `tape.mediaWidthMm`, not from `printAreaPx`/`dpi` — ticking off only the printable width would run past the edge of the visible tape. The ruler's container is sized from `mediaWidthMm` too (see the template's inline `height`), so ticks can never run past what's actually drawn.
|
||||
|
||||
### Calculated Short Link Fields
|
||||
`SHORT_URL_VAR` ("shortUrl") and `SHORT_ID_VAR` ("shortId") are Print.vue-local additions on top of label-layouts.js's own `DERIVED_VARS`. Resolving either needs the current `identityIdByHandle` map (populated by store.js's `fetchIdMap`) to turn a handle into the numeric `owner_identity_id` that short-id.js encodes — that dependency on Vuex state means neither can be a pure fields-to-value calculation like the rest of `DERIVED_VARS`, so both are computed here instead (see the `shortId` method and the `fields` computed). They're excluded from `baseVars`, unlike every other entry `KNOWN_VARS` normally picks up from a template's `required_vars`, because neither is ever typed directly into the form. `SHORT_URL_VAR` is filtered out again in the `baseVars`/`derivedVars` computed even though the "Short link (QR code)" template's `required_vars` puts it in `BASE_VARS` — it isn't actually a label-layouts.js `DERIVED_VARS` entry, it's calculated here. `SHORT_ID_VAR` isn't referenced by any template's `required_vars` today, but is filtered the same way in case one ever is. `SHORT_ID_VAR` itself is the bare short-id.js token (e.g. "~AbCd12") with no domain or leading "/" — for a label that wants just the compact code rather than a full scannable URL, printed only by "internal"-tagged templates meant for this same app/instance. `SHORT_URL_VAR` is not simply `webdomain + "/" + shortId` the way it looks: see ShortId Resolution below for why it's domain-qualified instead.
|
||||
`SHORT_ID_VAR` ("shortId"), `DOMAIN_SHORT_ID_VAR` ("domainShortId") and `SHORT_URL_VAR` ("shortUrl") are Print.vue-local additions on top of label-layouts.js's own `DERIVED_VARS`. Resolving any of them needs the current `identityIdByHandle` map (populated by store.js's `fetchIdMap`) to turn a handle into the numeric `owner_identity_id` that short-id.js encodes — that dependency on Vuex state means none can be a pure fields-to-value calculation like the rest of `DERIVED_VARS`, so all three are computed here instead (see the `shortId` method and the `fields` computed). They're excluded from `baseVars`, unlike every other entry `KNOWN_VARS` normally picks up from a template's `required_vars`, because none is ever typed directly into the form - `SHORT_URL_VAR`/`DOMAIN_SHORT_ID_VAR` are filtered out again in the `baseVars`/`derivedVars` computed even though some template's `required_vars` puts them in `BASE_VARS`, since neither is actually a label-layouts.js `DERIVED_VARS` entry.
|
||||
|
||||
These target three scopes, by how much context a reader needs before the value is useful at all: `SHORT_ID_VAR` is the bare short-id.js token (e.g. "~AbCd12") with no domain or leading "/" - **Home-Instance** scope, only meaningful to a reader already logged in as the identity it was minted for (printed by "internal"-tagged templates); `DOMAIN_SHORT_ID_VAR` prepends the printing user's own home domain (`homeDomain + ":" + shortId`, e.g. "a.example.com:~AbCd12") with still no URL wrapper - **Any-Instance** scope, resolvable by any Toolshed frontend's own scanner/resolver via a cross-domain lookup (short-id.js's `isDomainQualifiedShortId`/`decodeDomainQualifiedShortId`, see Domain-Qualified Short ID Resolution below) regardless of who's logged in there; `SHORT_URL_VAR` wraps that same bare `shortId` in `webdomain` as a clickable URL. `webdomain` only picks which frontend loads - it says nothing about which backend the data lives on, since that's decided entirely by whoever ends up logged in once it does - so `SHORT_URL_VAR` shares `SHORT_ID_VAR`'s Home-Instance trust requirement (same identity, just a tap instead of typed/scanned into an already-open session), not a step up to a universally-openable link. The actual zero-context scope is served elsewhere, by the long-form Item/Location URL (`url`, see Content Kinds Registry above), which resolves by handle rather than by the opener's own idmap.
|
||||
|
||||
### Libweblabel Served Unbundled
|
||||
`libweblabel.js` is served verbatim from `public/vendor/` rather than bundled by Vite. Its own emscripten glue resolves its `.wasm` sibling relative to *its own* `import.meta.url` at runtime, so both files need to keep sitting together, unhashed, at a stable URL rather than a Vite-fingerprinted asset path.
|
||||
|
|
@ -126,12 +131,12 @@ The tape-fed preview draws two representations of the tape: `.tape-full` (in the
|
|||
`RULER_TIERS` controls how far apart plain ticks and labeled/major ticks sit, both getting coarser the longer the ruler runs — tightly spaced ticks (and their labels) get too cramped to read or render once there are enough of them. The list is ordered smallest threshold first; `rulerTier` picks the last entry whose `aboveMm` the ruler's own length clears, so a finer or coarser tier is added there rather than growing a pile of separate constants. Every tier's `majorEveryMm` is a multiple of its own `tickMm`, so major ticks always land on a tick that's actually drawn. `rulerTier` itself is a single shared value keyed off whichever axis (`horizontalTotalMm`/`verticalTotalMm`) is physically longer, so a long label's ruler never ends up coarser or finer than the tape-width ruler right next to it just because the other axis happens to be shorter — both rulers coarsen together once *either* axis needs it.
|
||||
|
||||
### Fields Computed: Dropping Blank Values
|
||||
The `fields` computed builds the named content fields templates draw from: the form's own base vars, plus every `DERIVED_VARS` format string calculated live from those, so typing a `userHandle`/`itemId` (by hand or via prefill) recalculates `itemUrl`/`itemHandle` the same way either way. A blank or uncalculated value is dropped entirely rather than passed through as an empty string, so it reads as *absent* to `templateIsAvailable`/`templateContent` the same way a prefill that never supplied it would — that's what LabelLayoutPreview.vue greys a template's thumbnail out on.
|
||||
The `fields` computed builds the named content fields templates draw from: the form's own base vars, plus every `DERIVED_VARS` format string calculated live from those, so typing a `userHandle`/`kind`/`id` (by hand or via prefill) recalculates `url`/`qualifiedHandle` the same way either way. A blank or uncalculated value is dropped entirely rather than passed through as an empty string, so it reads as *absent* to `templateIsAvailable`/`templateContent` the same way a prefill that never supplied it would — that's what LabelLayoutPreview.vue greys a template's thumbnail out on.
|
||||
|
||||
### ShortId Resolution
|
||||
The `shortId` method builds the same shortened link Inventory.vue's and StorageLocation.vue's own `shortIdLink` build for one of their rows, but returns the bare token (see short-id.js's `encodeShortId`) rather than a router target or full URL — `shortenedRoute`'s leading "/" is stripped since this is plain display/label content, not something this view itself navigates to. Which short-id.js kind applies (`item` vs `storage_location`) depends on which id field the current prefill's `LABEL_FIELD_BUILDERS` populated (see label.js) — `itemId` vs `locationId` — rather than trusting the prefill's own `kind` directly, so hand-typing a `userHandle`+`itemId` with no prefill at all still resolves the same way. It falls back to no value, the same as an unresolved `DERIVED_VARS` entry, until `identityIdByHandle` has loaded (see `mounted`'s `fetchIdMap`) or if the handle isn't in it.
|
||||
The `shortId` method builds the same shortened link Inventory.vue's and StorageLocation.vue's own `shortIdLink` build for one of their rows, but returns the bare token (see short-id.js's `encodeShortId`) rather than a router target or full URL — `shortenedRoute`'s leading "/" is stripped since this is plain display/label content, not something this view itself navigates to. Which short-id.js kind applies depends directly on the Content card's `kind` field (via `CONTENT_KINDS_BY_ID`, see Content Kinds Registry above), not on which id-shaped field happens to be populated — so hand-typing a `userHandle`+`kind`+`id` with no prefill at all resolves exactly the same way a prefilled print link does. It falls back to no value, the same as an unresolved `DERIVED_VARS` entry, until `identityIdByHandle` has loaded (see `mounted`'s `fetchIdMap`) or if the handle isn't in it.
|
||||
|
||||
The `fields` computed doesn't just reattach *a* domain to build `shortUrl` from this same token, it reattaches the `homeDomain` computed specifically — the printing user's own home domain (`state.user`'s domain half), not `derived.domain` (the printed thing's own owner handle domain, which for a group-owned print is the group's domain, not necessarily the printer's). This matters because `owner_identity_id`/`owner_group_id` are resolved via `identityIdByHandle`/`groupIdByHandle`, both sourced from `state.idmap`, which always loads from the printing user's own home server (`getHomeServers`) regardless of whose item is being printed — so the domain a Domain-Qualified Short ID needs to carry is always the printer's home domain, the one whose KnownIdentity/Group numbering the encoded ints are actually scoped to (see docs/handles-and-shortids.md#domain-qualified-short-id). Getting this wrong (e.g. using `derived.domain` for a group print) would silently mint a token that resolves to the wrong backend, or to nothing, once scanned by someone else.
|
||||
`DOMAIN_SHORT_ID_VAR` reattaches the `homeDomain` computed specifically — the printing user's own home domain (`state.user`'s domain half), not `derived.domain` (the printed thing's own owner handle domain, which for a group-owned print is the group's domain, not necessarily the printer's). This matters because `owner_identity_id`/`owner_group_id` are resolved via `identityIdByHandle`/`groupIdByHandle`, both sourced from `state.idmap`, which always loads from the printing user's own home server (`getHomeServers`) regardless of whose item is being printed — so the domain a Domain-Qualified Short ID needs to carry is always the printer's home domain, the one whose KnownIdentity/Group numbering the encoded ints are actually scoped to (see docs/handles-and-shortids.md#domain-qualified-short-id). Getting this wrong (e.g. using `derived.domain` for a group print) would silently mint a token that resolves to the wrong backend, or to nothing, once scanned by someone else.
|
||||
|
||||
### Domain-Qualified Short ID Resolution
|
||||
A Domain-Qualified Short ID (`<domain>:~<token>`) resolving to something other than "the app I'm currently in" needs a real network round-trip, since the token's ints are only meaningful to whichever backend minted them (see docs/handles-and-shortids.md#domain-qualified-short-id). `router.js`'s `domainQualifiedRoute(domain, ints)` is the single chokepoint every entry surface (the `/:short_id` route's `beforeEnter` guard, `ShortId.vue`'s `resolveIfQualified`, `Scan.vue`'s `describeForeignShortId`) funnels through: if `domain` matches the caller's own home domain (`store.state.user`'s domain, read via `isLoggedIn` first to force its lazy hydration — see `getHomeServers`'s own comment on the same ordering issue) it resolves locally via the existing idmap-based `expandedRoute`, exactly like a bare token; otherwise it dispatches `resolveShortId` (`store.js`), which SRV-discovers `domain`'s backend (`getFriendServers`) and calls its new `GET /api/v1/resolve_short_id/<kind>/<owner_id>/<local_id>/` (`backend/toolshed/api/inventory.py`). That endpoint answers "what does this owner/local id pair mean" against its own KnownIdentity/Group numbering, applying the exact same friend-or-self/membership and `availability_policy` checks `InventoryItemViewSet.get_queryset()` already does (see Owner-Handle Scoped Routes below) — it only differs in being keyed by numeric owner id instead of a handle string, since the caller doesn't have a handle yet, that's exactly what it returns (`{handle, id}`). Only the four owned short-id kinds (`item`/`group_item`/`storage_location`/`group_storage_location`) are wired up; `group`/`category`/`file`/`workflow` return 400 and the frontend falls through to `ShortId.vue`'s debug view.
|
||||
|
|
@ -161,7 +166,7 @@ These coexist safely with their more specific neighbors (`/inventory/new/:group?
|
|||
`ownerOverviewRoute`/`ownerLocationOverviewRoute` (`router.js`) only ever pick between the caller and their own group, never a friend, since a friend's items/locations are read-only (see Perform Update Rejects Non-Owned Items Explicitly below).
|
||||
|
||||
### Item Short-Link Redirect Route
|
||||
The self-contained label/short-link entry point (see `label.js`'s `LABEL_CONTENT_BUILDERS` and docs/design-in-progress/items-labels.md). `:handle` is already URL-escaped the same way `/inventory/:handle/:id` expects it, so this is just a shorter alias for that route, with no owner-is-the-viewer special case: `InventoryItemViewSet.get_queryset()` (see Owner-Handle Scoped Routes below) already treats "it's the viewer's own item" as one case of "the viewer may see this owner's item," not a separate path.
|
||||
`/i/:handle/:id` and `/s/:handle/:id` are the self-contained label/short-link entry points for an item and a storage location respectively (see `label.js`'s `buildLabelContent`/`CONTENT_KINDS_BY_ID`, whose `buildUrl` for each `CONTENT_KINDS` entry builds exactly this shape, and docs/design-in-progress/items-labels.md). `:handle` is already URL-escaped the same way `/inventory/:handle/:id`/`/storage-locations/:handle/:id` expect it, so each is just a shorter alias for its own detail route, with no owner-is-the-viewer special case: `InventoryItemViewSet.get_queryset()`/`StorageLocationViewSet.get_queryset()` (see Owner-Handle Scoped Routes below) already treat "it's the viewer's own item/location" as one case of "the viewer may see this owner's item/location," not a separate path.
|
||||
|
||||
### `beforeEnter` Guard vs `redirect` for `/:short_id`
|
||||
This route uses a `beforeEnter` guard, not `redirect`: `redirect` is called synchronously and its return value is used as-is (never awaited), and it also *must* resolve to a valid location on every match (an unresolvable one throws, see vue-router's `handleRedirectRecord`) - it can't itself wait on `fetchIdMap` (see `NEEDS_IDMAP`) for the `item`/`group_item` kinds whose owner handle isn't resolvable from the token alone. A guard can return `null`/undefined to mean "proceed to the component instead," which is exactly what's needed here: when `expandedRoute` can't resolve yet (or ever - an unrecognized kind), stay on this same URL and mount `ShortId.vue` in place, which has full component-lifecycle async support and takes it from there - fetch idmap, retry, redirect once resolved, or keep showing the decode view. Vue Router guards support returning a Promise, not just a value, which is what makes this the right chokepoint for a Domain-Qualified Short ID too (see Domain-Qualified Short ID Resolution above): the guard is `async`, and for a foreign domain it `await`s `domainQualifiedRoute` (a real network round-trip) before deciding whether to redirect or fall through to `ShortId.vue`, the exact same "resolve or fall through" shape as the synchronous idmap case.
|
||||
|
|
@ -264,8 +269,5 @@ An inventory item's properties are encoded into the `properties` CSV cell as a c
|
|||
### Video Stream Attach And Resize Sync
|
||||
`setupVideoStream` in `Scan.vue` attaches a `MediaStream` to the `<video>` element and is shared by `startCamera` and the camera-switch/reconnect paths, so the `CameraScanner` (which just keeps reading frames off the same `<video>` element) never needs to be recreated when the camera changes. Right after `video.play()`, `video.videoWidth`/`videoHeight` are not populated yet - the browser only reports them once the video's own `resize` (and, on first load, `loadedmetadata`) event fires, confirming the intrinsic dimensions actually took effect. `onVideoResize` listens for both events and is the one place that resizes the overlay `<canvas>` to match the video's current rendered size, replacing whatever stale size it had from before a switch to a camera with a different native resolution/aspect ratio.
|
||||
|
||||
### Print Link Shape For Personal Items
|
||||
`printLinkFor` in `Inventory.vue` routes to `Print.vue` with the item's raw identity (`userHandle` + `item`) rather than any pre-built link - the same shape `InventoryDetail.vue`'s own Print label button sends. That lets the print page derive every representation it needs (item handle, owner handle, item URL, short link, …) itself, instead of being tied to whichever one the calling button happened to construct. Group-owned items have no individual owner handle - `short-id.js`'s `group_item` kind resolves them via `owner_group` instead (see `shortIdLink`) - so there's no `{userHandle, item}` to build yet; `printLinkFor` returns `null` for them until group print support exists.
|
||||
|
||||
### Print Link Shape For Storage Locations
|
||||
`printLinkFor` in `StorageLocation.vue` routes to `Print.vue` with the location's raw identity (`userHandle` + `location`), the same shape `Inventory.vue`'s `printLinkFor` sends for an item (see `label.js`'s `"storage-location"` entry in `LABEL_FIELD_BUILDERS`). Storage locations are always individually owned (see `StorageLocationViewSet.get_queryset`), so unlike the `Inventory.vue` version, this one never has to fall back to returning `null` for "no metadata available".
|
||||
### Print Link Shape
|
||||
`printLinkFor` in `Inventory.vue` and `StorageLocation.vue` (and the print buttons in `InventoryDetail.vue`/`StorageLocationDetail.vue`) all route to `Print.vue` with the same `{kind, userHandle, id}` query shape - the thing's raw identity - rather than any pre-built link. `kind` is one of `CONTENT_KINDS`' ids (`"item"`/`"storage-location"`, see Content Kinds Registry above); `label.js`'s `buildLabelFields` reads it generically since every kind's prefill now shares this one shape. That lets the print page derive every representation it needs (qualified handle, owner handle, URL, short link, …) itself, instead of being tied to whichever one the calling button happened to construct. Group-owned items have no individual owner handle - `short-id.js`'s `group_item` kind resolves them via `owner_group` instead (see `shortIdLink`) - so there's no `{userHandle, id}` to build yet; `Inventory.vue`'s `printLinkFor` returns `null` for them until group print support exists. Storage locations are always individually owned (see `StorageLocationViewSet.get_queryset`), so `StorageLocation.vue`'s version never has that fallback to make.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue