SEAM is architected around a strict separation of concerns that mirrors a traditional backend service pattern — but runs entirely in the browser. Data flows in one direction: a page calls a service, the service delegates to a repository, the repository callsDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/TheSerchCp/SEAM/llms.txt
Use this file to discover all available pages before exploring further.
ApiClient, and ApiClient speaks to the REST backend. Going the other way, server-pushed Socket.IO events are normalised by EventBus into an internal publish/subscribe channel that any page can subscribe to without coupling itself to the socket transport. The result is a codebase where every file has a single, clear responsibility and can be understood in isolation.
Project Structure
The full directory tree matches the separation of concerns described below:Architectural Layers
SEAM’s runtime follows a five-layer stack. Each layer depends only on the layer below it:Pages (js/pages/)
Each module directory contains at minimum a
*.page.js file that exports an async function returning an HTML string. The router calls that function, assigns the result to app.innerHTML, and the browser renders it. Pages import services for data and call registerPageCleanup() to unsubscribe from EventBus before the next navigation.Services (js/services/)
Service modules contain business logic: validation, data transformation, and orchestration across multiple repositories. Pages never call repositories directly — all data access is mediated through a service, keeping pages thin and testable in isolation.
Repositories (js/repositories/)
Repository modules are responsible for one thing: making HTTP requests and returning raw response data. They import
API_BASE from js/config/api.js and session.token for the Authorization header. No transformation or business logic lives here.ApiClient / config (js/config/)
js/config/api.js exports the single API_BASE constant that all repositories reference. Centralising the base URL here means switching environments (local → staging → production) requires editing exactly one line.State (js/state/)
session.state.js exports a plain mutable object (session) that holds user, token, permissions (a Set), and sidebarItems. It is hydrated from localStorage in main.js at startup and is the single source of truth for authentication status across all layers.Entry Point
js/main.js is the application bootstrap. It runs once when index.html loads the <script type="module"> tag:
js/main.js
localStorage, Socket.IO reconnection if a token is already present, and router initialisation by binding hashchange and calling loadRoute() immediately for the current URL.
SPA Routing
The router injs/core/Router.js maps URL hash fragments to page module functions. The route table is defined as a plain object literal:
js/core/Router.js
- The browser fires a
hashchangeevent when the user clicks a link or callslocation.hash = '#/route'. loadRoute()runs any cleanup functions registered by the previous page viaregisterPageCleanup().- The hash fragment is sliced (
location.hash.slice(1)) and the base segment (e.g./proyectos) is looked up inroutes. - Unauthenticated access to any route other than
/loginor/redirects to#/login. - An already-authenticated user hitting
/or/loginis redirected to#/home. - The matching page function is called (awaited if async), and its HTML string result is written to
document.getElementById('app').innerHTML.
LoginPage, HomePage, and UsersPage are eagerly imported at module load time. All other pages use dynamic import() expressions so their JavaScript is only fetched from the server the first time a user navigates to that route.Dual Layout System
SEAM has two layout wrappers that wrap the page content string before it is written to#app.
PublicLayout is used for unauthenticated pages (currently the login screen). It is a transparent pass-through — it returns the content string unchanged, making it trivial to add a public header or splash background in one place later:
js/layout/PublicLayout.js
PrivateLayout wraps every authenticated page in a full application shell consisting of a sticky Header, a fixed-left Sidebar (visible on md and above), a scrollable main content area, and a sticky Footer:
js/layout/PrivateLayout.js
Sidebar reads session.sidebarItems — the role-scoped navigation list returned by the backend on login — and renders only the items the current user is permitted to see.
Real-Time Layer: Socket.IO + EventBus
SEAM’s real-time communication is decoupled into two layers so that page components never need to import or reference Socket.IO directly.EventBus (js/core/EventBus.js) serves a dual purpose:
- Internal pub/sub — any module can call
EventBus.on('eventName', handler)andEventBus.emit('eventName', data)to communicate without direct imports. - Socket bridge —
EventBus.connect(token)opens a Socket.IO connection, authenticates it with the JWT, and re-emits incoming socket events on the internal bus.
main.js calls EventBus.connect(session.token) if a token is already present (page reload). On fresh login the authentication page calls EventBus.connect(token) after receiving the JWT from the backend.
The Toast.js shared component imported in main.js subscribes to notification events on the bus, meaning any part of the application can push a toast message simply by emitting the right event — the wiring to the DOM and the Socket.IO transport is completely hidden from the caller.
Static File Server
server.js is a zero-dependency Node.js HTTP server written with the built-in http, fs, and path modules. Its key behaviours are:
- Extension resolution — URLs without an extension are tried with
.js,.html, and.jsonin that order before falling back toindex.html. - SPA fallback — extensionless paths that match no file return
index.html, allowing the client-side router to handle the route. - Security — every resolved path is checked to confirm it starts with
__dirname, preventing directory traversal attacks. - No-cache headers — all responses include
Cache-Control: no-cacheso the browser always fetches the latest source files during development. - Port — listens on 3000, separate from the backend API on 3001.