Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Web app

Explanation

User UI state

How ui-state.js and the auth ui_state routes keep a user's home layout, dock, density, and search history on the server, and which preferences stay in the browser.

A user's arrangement of the web app belongs to the user account, not to one browser. The client module webapp/js/ui-state.js reads and writes named values through two routes of the auth module, and the server stores them per user in the table auth_user_ui_state. This page explains the data model, the routes, the write coalescing, the conflict rule for the home layout, and which preferences are not stored on the server.

In short

  • One row holds one key and one JSON value for one user; the key is unique among live rows of that user (src/cluster_erp/modules/auth/ui_state/models.py:13-35).
  • GET /api/v1/auth/ui-state returns every key of the signed-in user; PUT /api/v1/auth/ui-state/{key} stores one value (src/cluster_erp/modules/auth/ui_state/routes.py:21-39).
  • Both routes take the user id from the session, never from the request, and require authentication only (src/cluster_erp/modules/auth/ui_state/routes.py:15-18).
  • A stored value may be at most 262,144 bytes as JSON (src/cluster_erp/modules/auth/ui_state/schemas.py:8-29).
  • The client batches writes for 600 ms and flushes pending writes when the page is hidden or closed (webapp/js/ui-state.js:41-73).

How it works

Server side

Part Content Source
Model UserUiState Table auth_user_ui_state; user_id (foreign key to auth_users.id), key (up to 60 characters), value (JSONB), plus the base columns src/cluster_erp/modules/auth/ui_state/models.py:13-35
Unique index uq_auth_user_ui_state_user_id_key_live on user_id and key where deleted_at IS NULL src/cluster_erp/modules/auth/ui_state/models.py:24-29
Schema UiStateWrite value of any JSON shape, rejected when not serialisable or larger than _MAX_UI_STATE_BYTES src/cluster_erp/modules/auth/ui_state/schemas.py:10-29
Service UiStateService.all_for Returns {key: value} for live rows of one user src/cluster_erp/modules/auth/ui_state/services.py:19-28
Service UiStateService.put Inserts a row or updates the live row's value src/cluster_erp/modules/auth/ui_state/services.py:30-44
Route registration Included in the auth module router under /api/v1/auth src/cluster_erp/modules/auth/module.py:55, src/cluster_erp/modules/auth/module.py:68, src/cluster_erp/core/module_base.py:37-38

The base columns and soft delete come from the shared base model; see base model and ids. An update of a value is recorded by the automatic change history like any other mapped entity; see change history.

Client side

loadUiState() reads all keys once per session and shares one request among early callers. A failed read returns null and leaves the local copy in use (webapp/js/ui-state.js:18-39). saveUiState(key, value) updates the in-memory copy, queues the value, and restarts a 600 ms timer; the flush sends one PUT per key with keepalive so that a write survives a page reload (webapp/js/ui-state.js:41-73). visibilitychange to hidden and pagehide flush the queue at once (webapp/js/ui-state.js:58-65). Logout drops the cache and the queue (webapp/js/ui-state.js:75-85).

Keys written by the web app

Key Content Writer Source
springboard PC home layout {pages, updatedAt} save webapp/js/home.js:609-616, webapp/js/device-class.js:23
springboard_tablet Tablet home layout save webapp/js/device-class.js:24
springboard_phone Phone home layout save webapp/js/device-class.js:25
dock_pins Ordered list of pinned app ids setDockPins webapp/js/home.js:1004-1008
dock_switcher Whether the floating button lives in the dock setSwitcherDocked webapp/js/home.js:1033-1038
launcher Dock edge and auto-hide flag applyAutohide, setEdge webapp/js/app-wheel.js:158-161, webapp/js/app-wheel.js:563-567
density Row density tier setDensity webapp/js/prefs.js:90-99
spot_recent Up to 8 recent search picks rememberRecent webapp/js/home.js:1135-1157

Widget settings such as the Mine or All scope are stored inside the layout value, on the widget item's cfg (webapp/js/widget-scope.js:4-8).

Home layout conflict rule

The home layout is cached in local storage and stored on the server. hydrateFromServer() decides which copy wins (webapp/js/home.js:4565-4631):

  1. Dock pins, recent searches, density, and the docked button are taken from the server as they are (webapp/js/home.js:4579-4589).
  2. If the server has no layout for this device class, a first-time class starts from the PC layout; otherwise the local copy is published to the server (webapp/js/home.js:4590-4615).
  3. If both exist, each carries an updatedAt stamp. The server copy replaces the local one only when it is strictly newer; a newer local copy is sent to the server (webapp/js/home.js:4616-4631).

A failed request changes nothing; the local arrangement is never discarded because the server could not be reached (webapp/js/home.js:4565-4570).

Preferences kept in the browser only

Preference Storage Key Source
Theme local storage cluster_theme webapp/js/prefs.js:5, webapp/js/prefs.js:61-65
Time zone local storage cluster_tz webapp/js/prefs.js:6, webapp/js/prefs.js:111-114
Language local storage cluster_lang webapp/js/i18n.js:9, webapp/js/i18n.js:80
Accessibility settings local storage cluster_a11y webapp/js/a11y.js:5, webapp/js/a11y.js:44-46
Floating button position local storage cluster_float_btn webapp/js/home.js:2936, webapp/js/home.js:2976-2979
Open apps after reload session storage cluster.session.open_apps webapp/js/app-session.js:17
Unsaved form drafts memory and session storage cluster.session.form_drafts webapp/js/form-drafts.js:18

Why it is built this way

  • The comment in webapp/js/ui-state.js:1-9 states that a layout kept only in local storage differed per browser, device, and host name, and that a cleared cache looked like a reset.
  • The model docstring states that only the owning user, or an administrator acting for them, can change a row (src/cluster_erp/modules/auth/ui_state/models.py:14-21).
  • The schema comment states that the size cap stops a user from storing arbitrary amounts of data in the per-user slot (src/cluster_erp/modules/auth/ui_state/schemas.py:8-9).

Rules and boundaries

  • The project's written engineering rules require browser test suites to use web-client/_ui_state_guard.mjs. The guard runs suites under a dedicated test account so that a suite never changes a real person's layout (web-client/_ui_state_guard.mjs:1-4). See e2e battery.
  • Browser tests: web-client/_e2e_dock_pins.mjs, web-client/_e2e_app_wheel.mjs, web-client/_e2e_density.mjs, web-client/_e2e_mobile_home.mjs, web-client/_e2e_command_palette.mjs.

Interaction with other parts

Limits and open points

  • Keys are free strings chosen by the client, between 1 and 60 characters; a longer key is rejected with HTTP 422 before the handler runs (src/cluster_erp/modules/auth/ui_state/models.py:34, src/cluster_erp/modules/auth/ui_state/routes.py:33).
  • No route deletes a key. Not determined from code: whether keys of retired features are ever removed.

Terms introduced

Term Code identifier Definition Turkish term Source
UI state UserUiState A named JSON value that stores part of one user's interface arrangement on the server. kullanıcı arayüz durumu src/cluster_erp/modules/auth/ui_state/models.py:13
layout stamp updatedAt The time stamp that decides whether the server or the browser copy of the home layout wins. düzen damgası webapp/js/home.js:612

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13