workleap-squide
Squide Framework
Squide is a React modular application shell. Use only documented APIs.
Core Concepts
- Runtime: The
FireflyRuntimeinstance is the backbone of a Squide application. Never instantiate directly — useinitializeFirefly(), which wires up plugins, logging, and the module lifecycle. - Modular Registration: Modules register routes, navigation items, and MSW handlers via a registration function, assembled by the host at bootstrapping.
- Public vs Protected Routes: Routes default to
protected(rendered underProtectedRoutes). UseregisterPublicRoute()for public routes. Protected routes fetch both public and protected global data. - Deferred Registrations: Navigation items dependent on remote data or feature flags use two-phase registration — return a function from the registration to defer items to a second phase.
Key Patterns
Host Application Setup
// host/src/index.tsx
import { createRoot } from "react-dom/client";
import { FireflyProvider, initializeFirefly } from "@squide/firefly";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { App } from "./App.tsx";
import { registerHost } from "./register.tsx";
const runtime = initializeFirefly({
localModules: [registerHost]
});
const queryClient = new QueryClient();
const root = createRoot(document.getElementById("root")!);
root.render(
<FireflyProvider runtime={runtime}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</FireflyProvider>
);
// host/src/App.tsx
import { AppRouter, useIsBootstrapping } from "@squide/firefly";
import { createBrowserRouter, Outlet } from "react-router";
import { RouterProvider } from "react-router/dom";
function BootstrappingRoute() {
if (useIsBootstrapping()) {
return <div>Loading...</div>;
}
return <Outlet />;
}
export function App() {
return (
<AppRouter>
{({ rootRoute, registeredRoutes, routerProps, routerProviderProps }) => (
<RouterProvider
router={createBrowserRouter([{
element: rootRoute,
children: [{
element: <BootstrappingRoute />,
children: registeredRoutes
}]
}], routerProps)}
{...routerProviderProps}
/>
)}
</AppRouter>
);
}
// host/src/register.tsx
import { PublicRoutes, ProtectedRoutes, type ModuleRegisterFunction, type FireflyRuntime } from "@squide/firefly";
import { RootLayout } from "./RootLayout.tsx";
export const registerHost: ModuleRegisterFunction<FireflyRuntime> = runtime => {
runtime.registerRoute({
element: <RootLayout />,
children: [PublicRoutes, ProtectedRoutes]
}, { hoist: true });
// HomePage and NotFoundPage are local page components
runtime.registerRoute({ index: true, element: <HomePage /> });
runtime.registerPublicRoute({ path: "*", element: <NotFoundPage /> });
};
Navigation Rendering
Important: RenderItemFunction signature is (item, key, index, level) => ReactNode and RenderSectionFunction is (elements, key, index, level) => ReactNode. These signatures are fixed and do not accept custom context parameters, but there could be fewer arguments. Use closures to access external values.
import { Link, Outlet } from "react-router";
import {
useNavigationItems, useRenderedNavigationItems, isNavigationLink,
type RenderItemFunction, type RenderSectionFunction
} from "@squide/firefly";
// Signature: (item, key, index, level) => ReactNode
const renderItem: RenderItemFunction = (item, key, index, level) => {
if (!isNavigationLink(item)) return null;
const { label, linkProps, additionalProps } = item;
return (
<li key={key}>
<Link {...linkProps} {...additionalProps}>{label}</Link>
</li>
);
};
// Signature: (elements, key, index, level) => ReactNode
const renderSection: RenderSectionFunction = (elements, key, index, level) => (
<ul key={key}>{elements}</ul>
);
export function RootLayout() {
const navigationItems = useNavigationItems();
const navigationElements = useRenderedNavigationItems(navigationItems, renderItem, renderSection);
return (
<>
<nav>{navigationElements}</nav>
<Outlet />
</>
);
}
Global Data Fetching
// Protected data
import { useProtectedDataQueries, useIsBootstrapping, AppRouter } from "@squide/firefly";
// ApiError and isApiError are app-specific; define them to match your API's error shape
function BootstrappingRoute() {
const [session] = useProtectedDataQueries([{
queryKey: ["/api/session"],
queryFn: async () => {
const response = await fetch("/api/session");
if (!response.ok) throw new ApiError(response.status);
return response.json();
}
}], error => isApiError(error) && error.status === 401);
if (useIsBootstrapping()) return <div>Loading...</div>;
return (
<SessionContext.Provider value={session}>
<Outlet />
</SessionContext.Provider>
);
}
// In App component, set waitForProtectedData
<AppRouter waitForProtectedData>...</AppRouter>
// Public data
const [data] = usePublicDataQueries([{ queryKey: [...], queryFn: ... }]);
<AppRouter waitForPublicData>...</AppRouter>
Deferred Navigation Items
export const register: ModuleRegisterFunction<FireflyRuntime, unknown, DeferredRegistrationData> = runtime => {
// Always register routes
runtime.registerRoute({ path: "/feature", element: <FeaturePage /> });
// Return function for deferred navigation items
return (deferredRuntime, { userData }) => {
if (userData.isAdmin && deferredRuntime.getFeatureFlag("enable-feature")) {
deferredRuntime.registerNavigationItem({
$id: "feature",
$label: "Feature",
to: "/feature"
});
}
};
};
// Execute deferred registrations in BootstrappingRoute.
// Wrap in useMemo — without it, a new object reference each render re-triggers all deferred registrations.
const data = useMemo(() => ({ userData }), [userData]);
useDeferredRegistrations(data);
See also: For error boundaries, testing patterns, and advanced navigation (multi-level, dynamic segments, active state), see references/patterns.md. For MSW setup, LaunchDarkly, Honeycomb, i18next, and Storybook integrations, see references/integrations.md. For plugin authoring and the full runtime API, see references/runtime-api.md.
Reference Guide
For detailed API documentation beyond the patterns above, consult the reference files:
references/runtime-api.md—initializeFireflyoptions, route registration options (hoist,parentPath,parentId), route properties, navigation item properties, and navigation registration options (menuId,sectionId)references/hooks-api.md— All Squide hooks: data fetching (usePublicDataQueries,useProtectedDataQueries), navigation, event bus, environment variables, feature flags, logging, routing, and i18next hooksreferences/components.md—AppRouterprops,FireflyProvider, helper functions (isNavigationLink,resolveRouteSegments,mergeDeferredRegistrations)references/patterns.md— Local module setup, error boundaries, MSW request handlers, and other common patternsreferences/integrations.md— LaunchDarkly (plugin, utilities, testing clients), Honeycomb, i18next, and Storybook integration details
Common Pitfalls
Skill maintainers: Before updating this skill, read ODR-0008. The body must stay under ~250 lines; new API content goes in the appropriate
references/file.
When working with Squide APIs, watch for these common mistakes:
-
useRenderedNavigationItemsfunction signatures: Must always be(item, key, index, level)and(elements, key, index, level). These do NOT accept custom context parameters. If external values are needed (route params, location, etc.), use closures or React hooks - never suggest adding parameters to these functions. -
Active state styling: Use React Router's
NavLinkand itsisActiveargument provided to theclassName/stylerender functions (for example,className={({ isActive }) => ... }). Do not suggest passing location/pathname as a context parameter. -
Dynamic route segments: Use the
resolveRouteSegmentshelper with closures to capture values likeuserId. Example pattern: create a higher-order function that returns aRenderItemFunction. -
Deferred registration runtime parameter: The deferred registration callback receives
deferredRuntimeas its first argument — this is NOT the sameruntimefrom the outer registration function. Always usedeferredRuntimeinside the deferred callback forregisterNavigationItem,getFeatureFlag, etc.