Skip to content

Frontend (Sylva Enterprise)

Sylva Enterprise (sylva-enterprise) is the primary web client: org-specific branding (domain, logo, CSS), course editor, participant flows, and integrations with the Identity Manager API, Firebase, and Wolfram backends.

Stack (see also Codebase statistics):

  • Quasar 1 (Vue CLI pipeline, Webpack extended via webpack.conf.js)
  • Vue 2 SPA, history router mode
  • Vuex with many feature modules under src/store/
  • Axios REST client and Wolfram clients (src/boot/api.js)
  • Firebase compat (Auth, Firestore, Analytics) after cloud config loads
  • Global SCSS entry src/css/app.scss → partials under src/css/components/

For known limitations, CSS trade-offs, and structural improvement ideas, see Frontend — CSS, structure, and tech debt.


High-signal folders under src/:

PathRole
boot/Application bootstrap: API clients, router guards, Firebase/cloud config, Quasar plugins, global directives and base components
router/index.js wires Vue Router + Sentry; private.js / public.js define route trees and meta for access control
layouts/Three Quasar q-layout shells: course player, admin/editor, public
pages/Top-level route targets (dashboard, course shell, manager, errors, analytics, …)
components/Majority of UI: nested editor chrome, module players, content types, dialogs, charts
store/Vuex root index.js registers feature modules (session, course, admin, theme, …)
css/Global SCSS: app.scss imports Quasar variables + component partials
mixins/Shared Vue mixins (e.g. cssThemes.js for org theming)
utils/Pure helpers (dates, project names, Creo helpers, …)

Components are grouped by domain (e.g. components/Menus/AdminMenu/..., components/ContentTypes/...) rather than a flat atomic design tree—use repo search and the route → page → layout chain to find entry points.


quasar.conf.js registers boot files in this order (they run before the root Vue instance mounts):

flowchart LR
  A[api] --> B[router-guard]
  B --> C[init]
  C --> D[plugins]
  D --> E[directives]
  E --> F[base-components]
  F --> G[notify-defaults]
  G --> H[tooltip-defaults]
Boot fileResponsibility
api.jsAPI, WEPC_API, WWE_API axios instances; session key in LocalStorage; SSO query stripping; window.$SYLVA_API
router-guard.jsbeforeEach: auth, archive rules, fetchProject when projectId changes, feature flags, completion redirects (see Authentication and access control)
init.jscloud-config fetch, Firebase initializeApp, theme Vuex, window._org, Sentry-friendly init paths
plugins.jsVuelidate, ApexCharts, InstantSearch, MathLive, YouTube/Vimeo, timeago, global prototypes
directives.jsCustom Vue directives
base-components.jsGlobally registered components
notify-defaults.js, tooltip-defaults.jsQuasar UI defaults

sequenceDiagram
  participant B as Browser
  participant Q as Quasar SPA
  participant API as Identity Manager /api
  participant CC as cloud-config
  participant FB as Firebase
  B->>Q: Load JS bundle
  Q->>API: GET cloud-config
  API-->>CC: Org theme, firebase snippet, org id
  Q->>FB: initializeApp + optional signInWithCustomToken
  Q->>API: REST with Authorization + _org
  API-->>Q: Projects, modules, content JSON
  1. API base URL is chosen in api.js (dev domain vs API_HOST from build env—see quasar.conf.js build.env).
  2. Cloud config drives white-label: logos, CSS variables, Firebase config JSON in custom_config.
  3. Authenticated calls use the stored access token; org scope is applied after session/cloud config (see auth doc for _org).

Routes are privateRoutes.concat(publicRoutes) in src/router/index.js. Private routes cover the logged-in app (dashboard, /p/:projectId player, /manage/:projectId staff tools, org admin, etc.); public routes cover login, signup, legal pages, and similar.

Participant vs staff vs public (conceptual)

Section titled “Participant vs staff vs public (conceptual)”
flowchart TB
  subgraph public [Public routes]
    P1[Login / signup / SSO callbacks]
  end
  subgraph participant [Participant — /p/:projectId]
    R1[start, modules, courseware, assessment, survey, poll, game]
  end
  subgraph staff [Staff — /manage/:projectId]
    R2[Editor, people, settings, results, …]
  end
  public --> participant
  public --> staff

Examples of meta on routes (see private.js):

  • requiresAuth — processed in the guard together with session state
  • requiresPlayCourse — participant course access
  • requiresStaff — course manager shell
  • denyArchive — block non-staff if project is archived
  • requiresCompletion, appFeature, minBrowserWidth — feature and UX gates

Child routes under Course.vue use short path segments (cw, as, su, po, ga) for module modes (courseware, assessment, survey, poll, game).


All three use q-layout + q-page-container + a two- or three-column grid (main-grid-layout*) with slots for left aside (navigation / module list), main page (router-view content lives in the parent page), and right aside (chat, CTAs, etc.).

LayoutFileTypical use
CourseMainLayoutCourse.vueParticipant flows under /p/... — top bar, fullscreen game mode, chat visibility on mobile
AdminMainLayoutAdmin.vueEditor and org admin — wheel/touch forwarded for complex interactions, optional preview class
PublicMainLayoutPublic.vueMarketing / auth-only pages without course chrome

MainLayoutCourse applies route name as a CSS class on the grid (:class="{ [$route.name]: $route.name }") so page-scoped layout tweaks are possible without duplicating layouts.


Root store (src/store/index.js) registers modules including:

admin, adminMenu, course, modules, session, banner, menu, colors, chat, timer, release, theme, settings, results, game, poll, report, databins, analytics.

Session and course are the usual starting points when tracing “where does current user / current project live?”. Theme is fed from cloud config and works with the cssThemes mixin (CSS custom properties for Quasar and brand colors).


  1. Quasar loads app.scss once (see quasar.conf.jscss: ['app.scss']).
  2. app.scss imports quasar.variables then a stack of partials (_global, _cards, _inputs, …).
  3. Per-component styles live in Vue SFC <style> blocks (often scoped, sometimes SCSS with deep selectors for Quasar internals).
  4. Runtime theming: mixins/cssThemes.js maps cloud theme.data into --q-color-* and --theme-color-* variables; Quasar palette names are resolved with getPaletteColor.

Build / framework notes (from quasar.conf.js)

Section titled “Build / framework notes (from quasar.conf.js)”
  • API_ENV selects default API_HOST (identity.sylva.ac, identity.ch.sylva.ac, api.dev.sylva.ac, identity.test.sylva.ac); API_HOST env overrides.
  • build.env passes DOMAIN, FORCE_PROD_API (from PREVIEW_PR_PROD), SENTRY, REGION, VERSION, etc. to the client bundle.
  • Webpack: extendWebpack from webpack.conf.js; jsonpFunction / library names avoid collisions when embedded.
  • A temporary crypto.createHash patch (md4md5) is documented in config as a workaround until Webpack 5 or Vite—treat as operational debt when touching the build.

Quasar framework: importStrategy: 'auto', plugins Dialog, Notify, LocalStorage, SessionStorage, Meta, AppFullscreen; icon/font extras include eva-icons, roboto-font, material-icons (and outlined).


VariablePurpose
DOMAINDev-only target domain for API/cloud resolution
API_HOSTOverride API hostname at build time
API_ENVPreset region/environment for default API host
PREVIEW_PR_PRODMaps to FORCE_PROD_API in client env
VERSIONwindow.SYLVA_VERSION
SENTRYEnables Sentry initialization branch in router

  • REST: this.$api / window.$SYLVA_API/api/... with withCredentials: true.
  • Wolfram: WEPC_API (/wepc-api), WWE_API (/wwe-api) — README refers to “WLS”; the boot file exports WWE_API for the scalable server path.
  • CDN / images: CDN_URL in api.js; $getImageKitUrl on window for ImageKit transforms (see init.js).

Customization includes domain, logo/icons, CSS (global + variables), SSO, and (per README) possible future custom API hooks. The same build can render different orgs because cloud-config is fetched at runtime.


  • Lint: npm run lint (see enterprise README).
  • E2E: Selenium under test/ (see test/README.md).