@burdenoff/fe-libs
Frontend primitives and domain libraries for all Burdenoff products.
Architecture
This is a Layer 1 package - a monolithic package with subpath exports for all primitives.
Package Structure
@burdenoff/fe-libs/
├── /workflow - Workflow primitives (Node, Canvas, Edge)
├── /tag - Tag components
├── /form - Form components
├── /button - Button primitives
└── /shared - Shared utilities (cn, types)
Usage
import { Button } from '@burdenoff/fe-libs/button';
import { mockProducts } from '@burdenoff/fe-libs/shared/utils/mockData';
import { cn } from '@burdenoff/fe-libs/shared';
Development
# Install dependencies
bun install
# Build library
bun run build
# Type check
bun run type:check
# Run all checks
bun run sanity
Components
Workflow Primitives
- WorkflowNode - Renders a workflow node with status indicators
- WorkflowCanvas - Canvas component for workflow builder with grid background
Button
- Button - Versatile button component with variants (default, destructive, outline, secondary, ghost, link)
Shared
- cn - Utility for merging Tailwind classes
shell-native (app shells only)
@burdenoff/fe-libs/shell-native is the shell-side native runtime: Capacitor
(iOS/Android), Electron desktop and PWA wiring in one entry point. App shells
import it; microfrontends never do — MFEs talk to the shell through
window.__burdenoffNativeBridge (@burdenoff/fe-libs/shared/native).
Boot it once in main.tsx, before React mounts:
import { bootNativeShell, registerDeepLinkNavigator } from '@burdenoff/fe-libs/shell-native';
const shell = bootNativeShell({
product: 'vibecontrols',
appName: 'VibeControls',
// Optional plugins are loaded ONLY through these loaders, so web-only shells
// never bundle them.
plugins: {
updater: () => import('@capgo/capacitor-updater'),
pushNotifications: () => import('@capacitor/push-notifications'),
},
push: { enabled: true, onToken: (token) => void registerDevice(token) },
runtimeHooks: { syncPendingOperations: refetchAll, saveState: flushDrafts },
});
// Inside the router:
registerDeepLinkNavigator((path) => navigate(path));
// After the first meaningful paint:
void shell.markAppReady();
Boot order (each step is individually try/caught — boot never throws): preview →
OTA notifyAppReady → auth-redirect intercept → external URL opener → native
bridge + platform classes → device info → status-bar theme listener → keyboard →
Android back button → deep links → network → app-state hooks → push listeners →
splash safety timer (native) → PWA service worker (web) → desktop shell boot.
Contracts the shells and MFEs can rely on:
- Window globals —
__burdenoffNativeBridge,__burdenoffDeviceInfo,__burdenoffOpenExternal(url),__burdenoffPushToken,__burdenoffPwaInstallPrompt. - Body classes —
platform-ios | platform-android | platform-web,platform-native,platform-preview,keyboard-open.<html>carriesdata-platformand (in preview)data-native-preview. - CSS var —
--keyboard-height(0px when closed). - Window events —
burdenoff:appReady,burdenoff:appStateChange,burdenoff:networkStatusChange,burdenoff:keyboardWillShow|DidShow|WillHide|DidHide,burdenoff:pushToken,burdenoff:pwaUpdateReady,burdenoff:pwaInstalled,burdenoff:pwaInstallAvailable. The shell listens forburdenoff:statusBarTheme.
Native preview flag
?__nativePreview=ios|android|pwa|desktop renders the installed-app chrome in a
plain browser (persisted in sessionStorage, cleared with off). It is
presentation only — real plugin calls stay gated on
Capacitor.isPluginAvailable, and the flag is ignored inside a real native app.
Build-time config generators
defineCapacitorConfig(nativeConfig), defineWebManifest(nativeConfig) and
renderServiceWorker(nativeConfig, version) are pure functions (no
@capacitor/* imports) that emit each product's capacitor.config.ts,
public/manifest.json and public/sw.js from its native.config.json
(NativeConfig).
Raw colours, canvas tokens and the shared ESLint guard
UI code uses semantic tokens only. createEslintConfig() from
@burdenoff/fe-libs/shared/config/eslint.config.mjs reports, as an error,
hex / rgb() / hsl() / oklch() literals and Tailwind palette classes
(bg-red-500, text-white, border-t-gray-200, …) in string literals and
template strings. Comments and test files are not checked.
- A repo that still carries raw-colour debt can pass
rawColors: 'warn'while it sweeps; treat that as a tracked, temporary opt-out. - A repo with a standalone ESLint config applies the same guard with
'no-restricted-syntax': ['error', ...rawColorRestrictedSyntax()].
Workflow builders, graphs and mappers use the canvas tokens in tokens.css.
Each one aliases an existing chart-* / status-* / surface token, so it
follows light/dark mode, the brand and the expressive themes:
| Group | Tokens |
|---|---|
| Canvas | --color-canvas-{bg,grid,selection,minimap-bg,minimap-node,minimap-mask,control-bg} |
| Node surface | --color-node-{bg,bg-hover,border,border-hover,border-selected,text,text-muted,handle,handle-connected} |
| Node category | --color-node-{trigger,action,logic,loop,data,integration,ai,human,utility,end,default} plus -subtle |
| Node state | --color-node-state-{idle,queued,running,success,warning,error,skipped} |
| Edge | --color-edge-{default,hover,selected,active,success,error,compatible,incompatible,label-bg,label-text} |
| Data type | --color-datatype-{string,number,boolean,object,array,date,binary,any} |
app-shell.css maps them to Tailwind utilities (bg-node-trigger,
stroke-edge-default, ring-node-state-running, …). A shell with its own
@theme imports @burdenoff/fe-libs/shared/styles/canvas-theme.css instead.
For React Flow props, SVG attributes and inline styles, use the helpers in
@burdenoff/fe-libs/shared/utils (nodeCategoryColor, edgeColor,
nodeStateColor, dataTypeColor, chartSeriesColor(chartSeriesIndex(key)))
and the literal class records (nodeCategoryClasses, nodeStateRingClasses,
dataTypeTextClasses).
Explicit product analytics and withdrawal
ComradeCircle's default observability configuration uses the shared Rybbit sink's explicit
event transport rather than the provider script's automatic capture. It sends only the five
reviewed comradecircle.* funnel facts, resolves known route templates, and never includes
resource IDs, query strings, fragments, page titles, referrers, cookies or bearer headers.
The public ingestion endpoint requires a public site ID, not a client-side API key.
The host must set privacyConsent: true and privacyConsentReopen: true. The latter keeps a
Privacy choices control visible after a decision so people can withdraw or change their choice.
Consent, GPC and DNT are checked before every event. Withdrawal and unmount abort outstanding
requests; the sink has no replay/retry queue. Alpha has no default site and does not reuse the
production site. MFEs still emit typed events through useEventBus() only.
eventEndpoint and routeTemplates are opt-in AppShellObservabilityConfig.rybbit options.
Unmatched routes fail closed in explicit mode. Other products keep their existing script mode;
this product adoption is not a claim that the legacy provider's automatic listeners support
revocation. The original script-loading criterion is replaced by explicit-ingestion verification
because removing a loaded third-party script does not remove the listeners it installed.
Tech Stack
- React 19
- TypeScript 5.9+
- TailwindCSS v4
- Radix UI (for accessible primitives)
- Vite 7 (for building)
Export migration (BOFF-9417)
Beginning with the first CalVer release containing BOFF-9417, the already
nonfunctional @burdenoff/fe-libs/workflow export metadata is explicitly retired. Both advertised
targets (dist/workflow.js and dist/workflow.d.ts) are absent from the verified
published 2026.1016.3 package. The former WorkflowCanvas/WorkflowNode implementation
was deliberately removed in source commit f0920f9a on 2026-01-15; this change does
not remove a working runtime implementation or restore that obsolete UI. Existing
published releases are unchanged. Usage outside the bounded local repository
inventory is unknown; this is not a claim that external consumers do not exist.
Current workflow-builder functionality belongs to @burdenoff/fluidgrids-fe-sdk
(WorkflowBuilder and WorkflowBuilderCore), not another microfrontend import. Those
APIs differ from the deleted generic canvas/node props and require an explicit
consumer migration; they are not a drop-in compatibility adapter. Consumers should
review their intended workflow behavior before choosing that API.
The existing @burdenoff/fe-libs/shared/utils/mockData subpath is retained. Its five
fixture arrays now have an explicit runtime build entry alongside their declarations.
The archive verifier checks literal export/bin targets in the actual npm tar before
publication; wildcard export patterns and universal consumer compatibility are not
certified by that existence check. Normal release automation assigns the concrete
CalVer version; this source note does not claim publication or host adoption.
Captured workspace actions
Use graphqlFetchPinned when a request belongs to a captured actor or
workspace. Its named export also makes an incompatible host-library pin fail
during the build. It refuses a missing or expired captured bearer before dispatch and
never substitutes the account bearer from profile storage, refreshes the account
session, or replays a response.
Specify authority and context through the typed request options; pinned mode
refuses alternate workspace/context overrides in extraHeaders. The caller
supplies the current credential after normal session renewal and
reviews a failed or ambiguous mutation before attempting it again.
For workspace authority, reuse the shared preparation guard:
const prepared = await prepareWorkspaceRequest({
url: resolveGatewayUrl('workspace', baseUrl),
authToken,
actorId,
workspaceId,
tenantId,
currentQuery: query,
signal,
});
prepared.assertCurrent();
const response = await graphqlFetchPinned({
gateway: 'workspace',
baseUrl,
query,
variables,
authToken,
workspaceId,
workspaceToken: prepared.workspaceToken,
signal,
});
prepareWorkspaceRequest binds profile, actor and workspace authority and checks
concurrent changes during token issuance. Pass the captured request context and
cancel obsolete reads when the surrounding identity changes. Partition and
remount UI query observers by actor/context as well; a response-cache key alone
does not bind a request's credentials. The default refreshable mode retains
profile-based session recovery. Existing explicit-token consumers need an
individual authority/refresh review before adopting pinned behavior; this additive
API does not retroactively repair those callers.