← Vault Index
Source: tools/team-os-spec.md

Team OS — Micro HRIS for Advisory OS

App Specification


Overview

Team OS is a lightweight people management tool for professional service practice owners. It tracks team interactions, goals, and generates evidence-based review summaries — eliminating the recency bias and memory-based reviews that plague small firms.

Domain: team-os.advisoryos.ai (frontend) / team-os-api.advisoryos.ai (backend)


Tech Stack

LayerTechnologyHost
FrontendReact + ViteVercel
BackendExpress.js + Node.jsRailway
DatabasePostgreSQLRailway
AuthJWT (access + refresh tokens)
EmailResend (password reset)

Design System (Advisory OS)

ElementValue
PrimaryCharcoal #1a1a1a
BackgroundCream #f9f7f4
AccentGold #b79d64
Heading FontCormorant Garamond
Body FontInter
Border Radius0px (sharp edges everywhere)
Card StyleGold left-border (3px), cream background
ScrollbarGold track on charcoal
LayoutDesktop only (no mobile optimization)

Category Badge Colors

CategoryBackgroundText
Winrgba(143,209,158,0.2)#2d6a4f
Concernrgba(239,68,68,0.15)#991b1b
Feedbackrgba(59,130,246,0.2)#1e40af
Commitmentrgba(168,85,247,0.2)#581c87
1:1 Meetingrgba(183,157,100,0.2)#7c6a3a
Absencergba(156,163,175,0.2)#4b5563

Goal Status Colors

StatusBackgroundText
On Trackrgba(143,209,158,0.2)#2d6a4f
At Riskrgba(251,191,36,0.2)#92400e
Completedrgba(183,157,100,0.2)#7c6a3a
Pausedrgba(156,163,175,0.2)#4b5563

Database Schema

users

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  name VARCHAR(255),
  reset_token_hash VARCHAR(255),
  reset_token_expires TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

team_members

CREATE TABLE team_members (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
  name VARCHAR(255) NOT NULL,
  role VARCHAR(255),
  status VARCHAR(20) DEFAULT 'active',  -- active, on_leave, former
  start_date DATE,
  email VARCHAR(255),
  notes TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

interactions

CREATE TABLE interactions (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
  team_member_id INTEGER REFERENCES team_members(id) ON DELETE CASCADE,
  category VARCHAR(20) NOT NULL,  -- win, concern, feedback, commitment, meeting, absence
  content TEXT NOT NULL,
  interaction_date TIMESTAMPTZ DEFAULT NOW(),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

goals

CREATE TABLE goals (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
  team_member_id INTEGER REFERENCES team_members(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  status VARCHAR(20) DEFAULT 'on_track',  -- on_track, at_risk, completed, paused
  target_date DATE,
  notes TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

review_snapshots

CREATE TABLE review_snapshots (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
  team_member_id INTEGER REFERENCES team_members(id) ON DELETE CASCADE,
  title VARCHAR(255) NOT NULL,  -- e.g. "Q1 2026 Review"
  period_start DATE,
  period_end DATE,
  summary_text TEXT,  -- saved narrative
  interaction_count INTEGER,
  win_count INTEGER,
  concern_count INTEGER,
  feedback_count INTEGER,
  commitment_count INTEGER,
  meeting_count INTEGER,
  absence_count INTEGER,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

API Routes

Auth

Team Members

Interactions

Query params for filtering:

Goals

Reviews

Health


Frontend Views

1. Team Roster (Default View — /)

The landing page. Grid of person cards showing everyone at a glance.

Card contents:

Actions:

Add Team Member modal:


2. Person Detail View (/team/:id)

Three-tab layout for a single person. Tabs: Interactions | Goals | Review

Person header (above tabs):


Tab: Interactions (default)

Reverse-chronological list of all interactions for this person.

Each entry shows:

Actions:

Log Interaction modal:


Tab: Goals

List of active goals for this person with status tracking.

Each goal row shows:

Actions:

Add Goal modal:


Tab: Review

Auto-generated review summary from interaction data. NOT AI-generated — this is simple data aggregation.

Summary counts grid (2 rows of 3):

| Wins | Concerns | Feedback |

| Commitments | 1:1 Meetings | Absences |

Each count box: large number (Cormorant Garamond), label below (Inter, small caps)

Date range selector:

Grouped interaction sections (below counts):

Each section only shows if there are entries. Each entry shows date + content.

Actions:


3. Auth Pages

Same patterns as Rapport: login is default if not authenticated, gold accent button, charcoal background for auth pages.


Frontend Architecture

client/
├── src/
│   ├── api/
│   │   └── index.js          # API client with auth headers
│   ├── components/
│   │   ├── Layout.jsx         # App shell with nav
│   │   ├── TeamCard.jsx       # Person card for roster grid
│   │   ├── InteractionEntry.jsx
│   │   ├── GoalRow.jsx
│   │   ├── ReviewSummary.jsx
│   │   ├── CategoryBadge.jsx
│   │   ├── StatusBadge.jsx
│   │   ├── Modal.jsx          # Reusable modal
│   │   └── DateRangeFilter.jsx
│   ├── pages/
│   │   ├── Roster.jsx         # Team roster grid
│   │   ├── PersonDetail.jsx   # Tabbed person view
│   │   ├── Login.jsx
│   │   ├── Register.jsx
│   │   ├── ForgotPassword.jsx
│   │   └── ResetPassword.jsx
│   ├── App.jsx
│   └── main.jsx
├── index.html
├── vite.config.js
└── vercel.json                # SPA rewrites

Backend Architecture

server/
├── index.js                   # Express app setup, CORS, routes
├── db.js                      # PostgreSQL pool
├── middleware/
│   └── auth.js                # JWT verification
├── routes/
│   ├── auth.js
│   ├── teamMembers.js
│   ├── interactions.js
│   ├── goals.js
│   └── reviews.js
└── package.json

Build Order

  1. Backend: Database + API
  1. Frontend: Team Roster View
  1. Frontend: Person Detail — Interactions Tab
  1. Frontend: Person Detail — Goals Tab
  1. Frontend: Person Detail — Review Tab
  1. Frontend: Auth Pages

Server Configuration

// Bind to 0.0.0.0 for Railway
app.listen(PORT, '0.0.0.0', () => {...})
// CORS - allow frontend domain
cors({
  origin: [
    'http://localhost:5173',
    'https://team-os.advisoryos.ai'
  ],
  credentials: true
})
// API URL detection (client-side)
const API_BASE = window.location.hostname === 'localhost'
  ? 'http://localhost:3001/api'
  : 'https://team-os-api.advisoryos.ai/api'

Deployment Checklist

Railway (Backend)

Vercel (Frontend)

DNS (advisoryos.ai)


Key Behaviors

  1. Interaction logging is fast — Modal opens, category dropdown + textarea, save. Under 10 seconds to log.
  2. Review tab is data-driven — No AI generation. Just counts and grouped lists from the interaction log within the selected date range.
  3. Snapshots preserve history — When you save a review, the counts and date range are frozen so you can compare periods later.
  4. Soft delete for team members — Setting status to "former" preserves all history. No data loss.
  5. All routes require auth — Except /health and /api/auth/* routes.
  6. Category badges are consistent — Same colors everywhere (team-os counts, interaction list, review sections).