Skip to content

Developer Guide

abbas0444 edited this page Sep 25, 2026 · 4 revisions

Developer Guide

Layout of the app

nexus_theme/
├── api.py                      # whitelisted theme and sound API
├── hooks.py                    # includes, apps-screen tile, doc events, boot session
├── install.py / uninstall.py   # Theme User role, menu items, desktop icon, assets
├── website.py                  # login page and website theming
├── permission_inspector/api.py # Permission Inspector API (System Manager only)
├── nexus_theme/doctype/…       # Theme Definition, preferences, Theme Settings
├── nexus_theme/page/           # theme_studio, sound_studio, nexus_permission_inspector
├── nexus_theme/workspace/      # the Nexus Theme workspace
├── public/js, public/css       # theme_manager, theme_switcher, theme_editor,
│                               # sound_manager, sound_studio, brand_kit, …
├── public/sounds               # 36 synthesised presets
├── utils/                      # contrast, css_safety, palettes, palette_generator, web_css
├── fixtures/theme_definition.json
├── tests/                      # pure unit tests (no site needed)
└── tests_site/                 # site-backed tests
tools/generate_sounds.py        # regenerates every preset sound
tools/generate_logo.py          # regenerates the logo set in logos/

Python API

All methods live in nexus_theme.api and are whitelisted, so they work from frappe.call, REST (/api/method/nexus_theme.api.<name>) and server scripts. They act for the logged-in user and need the Theme User role.

# Themes
get_available_themes()                       # defaults, owned, public
get_active_theme()                           # theme, mode, dark theme, overrides, dark_overrides, density, sidebar_collapsed, source
set_active_theme(theme_name, overrides=None) # apply a theme (+ optional colour overrides)
set_theme_mode("Automatic", dark_theme="dracula")
clear_active_theme()                         # back to Frappe's own look
save_custom_theme(payload, share_public=0)   # payload = the 11 colours + style fields
delete_custom_theme(theme_name)
export_theme(theme_name)                     # portable JSON
import_theme(payload, share_public=0)
get_recommended_palettes()                   # the 8 curated palettes
generate_palette(seed="#8c6f3f", is_dark=0)  # 3 accessible variants from one colour

# Mini rail
get_sidebar_collapsed()                      # {collapsed: 0|1}
set_sidebar_collapsed(1)

# Home page
get_home_data()                              # greeting name, tiles with to-do counts, settings

# Density
get_density()                                # {density: "compact", source: "user"|"site_default"|"default"}
set_density("compact")                       # key or label; empty means "follow the site default"

# What's new (nexus_theme.whats_new)
get_notes()                                  # the notes for the running release series
mark_seen()                                  # remember that this person has seen them

# Sounds
get_user_sounds()                            # enabled flag + event -> {url, volume}
set_user_sound("save", file_url="/files/pop.wav", volume=0.6)
clear_user_sound("save")
toggle_user_sounds(enabled=0)
clear_all_user_sounds()

Permission Inspector methods live in nexus_theme.permission_inspector.api and require System Manager:

get_options()
get_matrix(target_type="user", target="abbas@example.com", include_child=0)
get_matrix(target_type="role", target="Accounts User")
get_doctype_detail("user", "abbas@example.com", "Sales Invoice")   # rules, live check, user permissions
get_user_permissions("abbas@example.com")
save_changes([{"doctype": "Sales Invoice", "role": "Accounts User", "ptype": "write", "value": 0}])
refresh_cache(target_type="user", target="abbas@example.com")

save_changes validates every rule, cascades dependencies the way Frappe requires, saves the batch under a savepoint, clears the permission cache and returns the fresh matrix rows for the affected record types.

REST example

curl -s "https://yoursite.com/api/method/nexus_theme.api.get_available_themes" \
     -H "Authorization: token <api_key>:<api_secret>"

JavaScript API

window.openThemeSwitcher();     // open Theme Studio
window.openSoundStudio();       // open Sound Studio

ThemeManager.applyTheme("theme_key", { bg_primary: "#ffffff" }); // apply now
ThemeManager.handOffToFrappe();                                  // back to Frappe's look

SoundManager.applyMapping({ save: { url: "/files/pop.wav", volume: 0.7 } });
SoundManager.setEnabled(false);

NexusDensity.modes();           // [{ key, label, description }, …]
NexusDensity.get();             // "comfortable"
NexusDensity.set("compact");    // applies and saves; returns a Promise

window.openCommandPalette();    // open the palette
NexusCommandPalette.register({  // add a command of your own; returns an unregister function
	label: "Close the month",
	hint: "Accounts",
	keywords: ["period", "closing"],
	run: () => frappe.set_route("period-closing-voucher", "new"),
});
window.openNexusWhatsNew();     // show the "What's new" card again

NexusRail.isCollapsed();
NexusRail.set(true);            // fold the sidebar (saved); returns a Promise
NexusRail.toggle();
window.openNexusHome();

Registered commands appear under Actions in the palette. Density changes raise a nexus-density-change event on document with { detail: { density } }, and the page carries html[data-density] for your own stylesheets.

To open the Permission Inspector on a given person or role from your own code:

frappe.route_options = { role: "Accounts User" };   // or { user: "someone@example.com" }
frappe.set_route("nexus-permission-inspector");

Tests

# Pure unit tests, no site needed
python -m unittest discover -s apps/nexus_theme/nexus_theme/tests

# Site-backed tests (set allow_tests on the site first)
bench --site yoursite.com set-config allow_tests true
bench --site yoursite.com run-tests --app nexus_theme
bench --site yoursite.com run-tests --app nexus_theme --module nexus_theme.tests_site.test_permission_inspector
bench --site yoursite.com set-config allow_tests false

The site tests create their own roles, users and a throw-away DocType, and check the Permission Inspector against Frappe's own has_permission from the affected user's session. One test guards every page route against a collision with a DocType name, which is what the Desk router resolves first.

Regenerating assets

python3 tools/generate_sounds.py   # rewrites public/sounds from the recipes in the script
python3 tools/generate_logo.py     # rewrites logos/ (needs Pillow and fontTools)
bench build --app nexus_theme      # rebuild the JS/CSS bundles after editing public/

Code style

The repository ships a pre-commit configuration with ruff (Python) and an ESLint configuration for the scripts. CI installs the app on a fresh site and runs the full test suite on every push to either branch: version-16 builds a Frappe 16 bench, version-15 a Frappe 15 one, and each asserts the framework version it got before running the tests.

pre-commit install
pre-commit run --all-files

Clone this wiki locally