@feedmepos/mf-media
v2.0.7
Published
Keywords
Readme
media-portal
Vue 3 microfrontend, published as @feedmepos/mf-media and mounted into the portal shell
(mf-portal-root) via import map at /media. Two tabs:
| Route | View | What it is |
|---|---|---|
| /library | views/AssetLibrary.vue | the asset centre — upload, tag, search, delete |
| /studio | views/AiStudio.vue | the AI image studio — compose from a prompt |
The tab bar lives in App.vue because it is this module's own chrome, and App.vue is what
app.ts exports as FmApp for the shell to mount. The active tab is derived from the route,
so a deep link, the back button and a click cannot disagree.
Running it
pnpm dev # standalone dev server on :5190
pnpm type-check
pnpm test # node --test over test/*.test.ts
pnpm build:mf # the published artifactStandalone dev still requires a real portal sign-in: the studio reads
coreStore.currentBusiness, so without a session there is no businessId and nothing can be
generated. src/main.ts points at the local Go backend per .env.development.
To exercise the studio you need the backend too, and with no GEMINI_API_KEY it uses the
Stub, which renders proportioned placeholders for free:
cd ../media-backend && GOTOOLCHAIN=go1.23.9 go run .What verification here actually covers
Three gaps are worth knowing, because each one silently passes work that is wrong:
pnpm lintdoes not run. There is no ESLint config anywhere in this repo, so ESLint 8 parses every file as ES5 and fails on the firstimport— about 20 errors on a clean checkout, in files no feature touches. Not a gate; fixing it is separate work.pnpm type-checkdoes not checkFm*component props.<FmButton size="not-a-real-size">passes clean. Read the component's own.d.tsundernode_modules/@feedmepos/ui-library/dist/components/before using a prop. Things a guess gets wrong:FmButton.sizeis'md' | 'lg'with no'sm', itsiconprop is deprecated in favour ofprependIcon,FmCircularProgress.sizestarts at'md', andFmChipselection isselected+selectable.pnpm type-checkdoes not check anything from@feedmepos/media-pickereither. That package'sexports.typespoints at adist/index.d.tsits build never emits, so the whole module resolves asany. And because the portal resolves it through the builtdistrather than source, any change to media-picker needspnpm picker:buildfrom the repo root before the dev server or a build can see it.
So pnpm build:mf and the browser are the only real checks on a template. Note that
build:mf only compiles reachable modules — a component nothing imports yet is not being
verified by it.
Spacing numbers are px, and the scale is sparse
The design system's Tailwind plugin sets theme.extend.spacing to a px-valued scale:
0 4 8 12 16 24 32 40 48 56 64 72 80 88 96 104 112 120It arrives through extend, not as a replacement, so a number not on that list silently
falls through to Tailwind's own rem scale and lands about four times too large. gap-20 is
not 20px, it is 80px. h-36 is 144px. w-44 is 176px.
This cost real time: an 80px-padded 320px panel left ~160px of content, split into two columns, which wrapped every label to one character per line. Nothing warns — the classes are real Tailwind, just not the values the design intends. When translating a mock's pixel values, check each number against the list above.
Arbitrary values (w-[320px], bg-[--fm-color-brand-primary,#FF7823]) do work and are the
established idiom here — see AssetGrid.vue. Use them for anything off-scale.
Studio state survives a tab switch
The batches live in stores/generation.ts, not in AiStudio.vue's setup, so leaving for the
asset centre and coming back does not throw away the run. The poll loop is deliberately not
stopped on unmount: a batch generating while the merchant looks at the library should be
finished when they return, not frozen where they left it.
Read it with storeToRefs, never plain destructuring — a Pinia store is a reactive proxy, and
pulling batches straight out hands the template a snapshot that never updates.
Layout
src/
App.vue the tab bar over <RouterView>
router/shared.ts both routes, each permission-wrapped
views/AssetLibrary.vue asset centre
views/AiStudio.vue studio page — layout only, no logic
composables/
generationState.ts pure state transitions (tested)
useGeneration.ts reactive shell: refs, poll timer, network
components/studio/ the studio's five components
api/generation.ts the three studio endpoints
locales/*.json 7 locales, identical key sets
test/
generationState.test.ts node --test, no vitestgenerationState.ts is split from useGeneration.ts so the transitions with edge cases can
be tested without Vue — the same division packages/media-picker draws between variants.ts
and useAssetVariants.ts. The saved-preserving merge is the reason it earns its keep: a
poll landing a second after a save would otherwise flip the button back to "save", which
nobody would reproduce by clicking around.
