Files
zooqueen e937ba8c71 a new account is met by a card naming what is here
Four lines on the empty chat: the model picker, the modes and their sources,
/agent, and the sidebar. It dims nothing and gates nothing -- the arrival modal
was removed by owner call, and this sits beside the product rather than in
front of it.

`toured` carries no schema default. `false` shows the card, `true` has seen it,
and ABSENT is an account older than the card, which never gets one; a default
reaches every record that lacks the field, which is every account that already
exists. `reconcileUser` writes `false` at creation, so exactly the accounts born
from here on qualify. The client tests `=== false` for the same reason, and the
spec fails when that is weakened to a falsy check.

It rides out on `GET /v1/chat/user`, which already sends the projected document,
so there is no second GET to keep in step -- one POST records the dismissal, and
the cached user is written on mutate so the card goes on the click rather than a
round-trip later.

In the flow, under the composer, not floating over it. The composer block is
min-h-[280px] at the bottom of that column and its input is capped and centred,
leaving ~142px of gutter at 1440 against a card that needs 304 -- measured in
Chromium against the built stylesheet: 288x252 at 16px from the column edge,
clear of the composer at 1440 and at 390, no sideways scroll, 17.25:1 on the
title and 12.82:1 on the lines.
2026-08-17 16:38:35 -07:00

47 lines
1.7 KiB
JavaScript

const express = require('express');
const {
updateUserPluginsController,
getTermsStatusController,
acceptTermsController,
tourController,
deleteUserController,
getUserController,
} = require('~/server/controllers/UserController');
const {
configMiddleware,
canDeleteAccount,
requireJwtAuth,
requireGuestOrJwtAuth,
} = require('~/server/middleware');
const settings = require('./settings');
const router = express.Router();
router.use('/settings', settings);
router.get('/', requireGuestOrJwtAuth, getUserController);
router.get('/terms', requireJwtAuth, getTermsStatusController);
router.post('/terms/accept', requireJwtAuth, acceptTermsController);
router.post('/tour', requireJwtAuth, tourController);
router.post('/plugins', requireJwtAuth, updateUserPluginsController);
router.post('/active-org', requireJwtAuth, (req, res) => {
// Pin the active org for downstream api.hanzo.ai calls. Fail closed: the org must
// be in the caller's own membership set (owner + IAM `groups`); the gateway then
// re-validates X-Org-Id ∈ the verified JWT membership set (HIP-0026).
const requested = String(req.body?.organization ?? '').trim();
const allowed = new Set([req.user?.organization, ...(req.user?.groups ?? [])].filter(Boolean));
if (!requested || !allowed.has(requested)) {
return res.status(400).json({ error: 'organization not in your memberships' });
}
res.cookie('hanzo_active_org', requested, {
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
maxAge: 1000 * 60 * 60 * 24 * 30,
});
return res.json({ organization: requested });
});
router.delete('/delete', requireJwtAuth, canDeleteAccount, configMiddleware, deleteUserController);
module.exports = router;