# BeanOpsGate — Feature Inventory

BeanOpsGate (`beanops.bbtl.app`) is the admin portal + API gateway for the
BeanOps Worklance desktop (Electron) app. Two databases:

- `bbtl_beanops` — writable. Native users, RBAC, companies, screenshots, sessions.
- `bbtl_workspace` — read-only. The shared workspace (users + tasks).

---

## 0. Access levels

| Level | Who | Can do |
|-------|-----|--------|
| **Super Admin** | Hardcoded `users.id = 1` (the developer). Wins over any role/permission. | Everything. Switch companies (header switcher), manage the company list (`/admin/companies`), manage users in any company. |
| **Admin** | Any user with `is_admin = true` (a company may have many). Pinned to one company. | Sign in to the web portal, manage **their own company's** users (`/admin/users`). No company switching, no `/admin/companies`. |
| **Desktop user** | Everyone else (incl. synced workspace users). | Desktop (Electron) app only — no web access. |

- `App\Models\User::isSuperAdmin()` → `id === 1`; `isAdmin()` → super admin or
  `is_admin`. `accessLabel()` → "Super Admin" / "Admin" / null.
- The old `is_super_admin` column was renamed to `is_admin`
  (`...130000_rename_is_super_admin_to_is_admin_on_users`).
- `/admin/companies` + company switching are gated by the `super.admin`
  middleware (`App\Http\Middleware\EnsureSuperAdmin`); only the Super Admin sees
  the Companies sidebar item and the header company switcher.
- Non-super-admins are locked to their own company by `App\Support\ActiveCompany`
  and can only touch users in that company (`UserController::authorizeSameCompany`).

## 1. Authentication split — admin portal vs. desktop app

- **Web login is admins-only.** `LoginRequest` runs a native `Auth::attempt`,
  then logs out + rejects anyone who isn't an admin (`isAdmin()` — the Super
  Admin or an `is_admin` user). See the access table above.
  - `app/Http/Requests/Auth/LoginRequest.php`
- **Workspace web-login removed.** The "Login as workspace user" button and the
  `mode=workspace` path are gone; the `WorkspaceAuthUser` model was deleted. The
  login screen now reads "Administrator access only."
  - `resources/views/auth/login.blade.php`
- **Electron login authenticates against `bbtl_beanops.users`** (`App\Models\User`)
  for any user. A stateless AES bearer token resolves the native user.
  - `app/Http/Controllers/Api/AuthController.php`
  - `app/Http/Middleware/DesktopTokenAuth.php`
  - `app/Support/DesktopToken.php`

## 2. Sync Workspace Users

- `UserController::syncWorkspace()` copies `bbtl_workspace` users into
  `bbtl_beanops` as `source='workspace'`, **carrying the bcrypt password hash** so
  the same credentials work against the desktop API. New shadow users land in the
  active company; existing rows keep their company.
- Surfaced as a **"Sync Workspace Users"** button on the Users admin page
  (`admin.users.sync-workspace`).
  - `app/Http/Controllers/Admin/UserController.php`

## 3. Desktop task routing

- `Api/TaskController`:
  - `source = workspace` → live workspace tasks via `workspace_user_id`
    (dynamic tasking).
  - native → a single synthetic **"Default Task"** (covers silent / auto mode).
- `User::toProfile()` returns a `dynamic_tasking` flag so the client knows which
  mode it's in.
  - `app/Http/Controllers/Api/TaskController.php`
  - `app/Models/User.php`

## 4. Company management UI (single `company_id` model)

- **Companies page** — list / create / edit / activate-deactivate / delete +
  "switch to this".
  - `app/Http/Controllers/Admin/CompanyController.php`
  - `resources/views/admin/companies/index.blade.php`
- **Users page** — list / create / edit / delete scoped to the active company,
  with role + company assignment and the Sync button.
  - `resources/views/admin/users/index.blade.php`
- **Active-company switcher** in the top header, backed by a session-scoped
  `active_company_id` helper.
  - `resources/views/components/company-switcher.blade.php`
  - `app/Support/ActiveCompany.php`
- **Administration** section in the sidebar (Companies, Users).
  - `config/menu.php`

---

## Supporting infrastructure (pre-existing)

- **RBAC** — `companies`, `roles` (company-scoped), `permissions`,
  `permission_role`, `role_user`, custom `HasRoles` trait, `is_super_admin`.
  Seeded by `BeanOpsAuthSeeder`.
- **Live Screens** — `LiveScreenController` + `LiveScreenService`: admin requests
  a session, agent ingests frames, cache-backed live/tracking status, latest
  frame on a private disk. Endpoints: `start / ping / stop / frame / data / ingest`.
- **Screenshots** — desktop upload API (`Api/ScreenshotController`) + admin
  gallery (`ScreenshotViewController`).
- **Profile / Interface settings** — avatar, timezone, password; per-user
  `InterfaceSetting` (Livewire).
- **Read-only workspace layer** — `Workspace\{WorkspaceUser, Task, TaskType}`
  guarded by the `PreventsWrites` concern.

---

## Open items

- **Live Screens viewer still uses Alpine.js**
  (`resources/views/live-screens/index.blade.php`) — source of the
  `frameUrl / viewing / stale is not defined` console errors, the
  "loading forever" behaviour, and a `beanops.css` 404. Planned rewrite: plain JS,
  ~1 frame every 2 seconds, no Alpine.
