Skip to main content
Back to Journal
user@argobox:~/journal/2026-02-22-modularization-and-settings
$ cat entry.md

Modularization: Building Foundations That Scale

○ NOT REVIEWED

Modularization: Building Foundations That Scale

The Extraction Problem

Yesterday I'd built a comprehensive job automation system. It worked. But it was embedded in JobSpyAdvanced-Lab, which is itself embedded in my personal infrastructure. Not portable. Not really.

The system needed to exist as a standalone package. Call it applyr. Something you could pip install, configure with environment variables, and run independently.

Spent about two hours pulling it apart.

What Changed

The internal version assumed everything was relative to BASE_DIR = Path(__file__).parent.parent. Hardcoded resume paths. Embedded BrightData proxy credentials. Everything coupled to the parent project.

The extracted version:

  • DATA_DIR = Path(os.getenv('APPLYR_DATA_DIR', Path.cwd() / 'data')) — You point it to where your data lives
  • All env vars prefixed APPLYR_* instead of AUTOAPPLY_*
  • Created pyproject.toml with hatchling build system
  • Added a CLI: applyr serve, applyr apply <csv>, applyr version
  • Supports python -m applyr for containerization

Verified:

  • pip install -e . succeeds
  • All module imports work
  • applyr serve starts the API on :8585
  • Health checks respond

It's now a real package. You could distribute it. Someone could use it without understanding ArgoBox internals.

The Module System for ArgoBox

But that raised a new problem: if applyr is standalone, how do we keep it in sync with ArgoBox?

The answer: a module system.

Created src/config/modules/ where each .ts file registers a feature. Currently just jobs.ts, but the pattern is extensible. Here's how it works:

  1. Each module file exports an AdminNavItem (or array of them)
  2. admin-nav.ts uses import.meta.glob('./modules/*.ts', { eager: true }) at build time
  3. Discovered modules get merged into the nav
  4. Core nav stays hardcoded; modules add on top
  5. Delete src/config/modules/jobs.ts + the jobs page + the jobs API route → Jobs disappears from sidebar
  6. Restore them → Jobs reappears

This isn't just for jobs. Any admin feature that's optional (content lab, tools, sandboxes) can become a module. Keeps the main codebase lean and makes it obvious which features depend on which services.

User Settings Infrastructure

Then I tackled settings. One of those things that's been vaguely planned for months but never executed.

Built a per-user preferences system backed by Cloudflare KV:

The Architecture

Data model in KV at data:user-preferences:{email}:

interface UserPreferences {
  version: 1;
  adminNav?: {
    items: NavItemOverride[];  // which items are visible, what order, custom groups
    groups: SidebarGroup[];     // custom organization
  };
  dashboard?: {
    widgetOrder?: string[];
    hiddenWidgets?: string[];
  };
  general?: {
    defaultPage?: string;       // where you land after login
    sidebarCollapsed?: boolean;
    compactMode?: boolean;
    theme?: { primary?: string; secondary?: string };
  };
  updatedAt: string;
}

60-second in-memory cache per user so we're not hammering KV on every page load. API endpoints at GET/POST /api/user/preferences with Cloudflare Access auth.

The UI

Created two settings pages:

Admin Settings (/admin/settings) with three tabs:

  • Navigation — Drag-to-reorder nav items via SortableJS, toggle visibility, assign custom groups
  • Dashboard — Drag-to-reorder widgets, toggle visibility
  • General — Default landing page, sidebar collapsed toggle, compact mode toggle, theme color pickers

User Settings (/user/settings) for members:

  • Dashboard and appearance tabs only
  • No navigation customization (members don't access admin sidebar)

SortableJS loaded from CDN on first drag interaction. Auto-save on change with 500ms debounce + status toast. All client-side state management with fetch API.

The Integration Point

CosmicLayout now resolves user preferences on admin pages (SSR only) and passes them to AdminSidebar. This means the sidebar renders with customized nav on first paint — no flash of default content, no layout shift.

If preference loading fails, gracefully falls back to canonical nav. Doesn't break the page.

The Session in Summary

Two separate infrastructure projects, both foundational:

Applyr extraction — Made job automation portable and independently deployable. No longer coupled to ArgoBox internals. Packagable, pip-installable, containerizable.

Module system — Created a pattern for optional features. Kill a module without breaking the core. Extend with new modules without touching core code. Makes ArgoBox more like a platform and less like a monolith.

Settings infrastructure — Built the foundation for per-user customization. Sidebar customization was the MVP, but the same pattern scales to dashboard widgets, theme preferences, default pages, anything that should be personal instead of global.

All three changes are invisible to end users. You open argobox.com tomorrow and nothing looks different. But the foundation is cleaner. The system is more modular. Future features have less resistance to implementation.

That's the work that doesn't feel glamorous but enables everything that comes after.

Still In Progress

End-to-end testing on the standalone applyr package. Need to run applyr serve from the new package instead of the old module and verify the full flow still works.

Settings pages live but widgets aren't actually using the preference data yet. Dashboard widget ordering will come next (easy implementation on top of this foundation).

Jobs module will eventually be its own git repository, pulled in as a git subtree or npm package. That's the long-term vision.

For now, it's modular. It works. It's ready for the next layer.


Status: Applyr v0.1.0 extracted and packaged. Module system functional. Settings infrastructure live, waiting for feature integration. Two architectural foundations laid in one day.