- Create Economist SubmissionTracking package correctly: * mainArticle = full blog post content * coverLetter = 216-word SIR— letter * Links to blog post via blogPostId - Archive 'Letter to The Economist' from blog posts (it's the cover letter) - Fix date display on article cards (use published_at) - Target publication already displaying via blue badge Database changes: - Make blogPostId optional in SubmissionTracking model - Economist package ID: 68fa85ae49d4900e7f2ecd83 - Le Monde package ID: 68fa2abd2e6acd5691932150 Next: Enhanced modal with tabs, validation, export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
456 lines
29 KiB
Markdown
456 lines
29 KiB
Markdown
# Phase 3: Project Context Awareness - Architecture Diagram
|
|
|
|
---
|
|
|
|
## System Architecture Overview
|
|
|
|
```
|
|
┌───────────────────────────────────────────────────────────────────────┐
|
|
│ PHASE 3 ARCHITECTURE │
|
|
└───────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ FRONTEND LAYER │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Navbar │ │ Rule Manager │ │ Project │ │
|
|
│ │ │ │ │ │ Manager │ │
|
|
│ │ ┌──────────┐ │ │ • View Rules │ │ • List │ │
|
|
│ │ │ Project │ │ │ • Rendered │ │ • Create │ │
|
|
│ │ │ Selector │ │ │ Text │ │ • Edit │ │
|
|
│ │ │ Dropdown │ │ │ • Template │ │ • Delete │ │
|
|
│ │ └────┬─────┘ │ │ • Variables │ │ │ │
|
|
│ └──────┼────────┘ └──────┬───────┘ └──────┬───────┘ │
|
|
│ │ │ │ │
|
|
│ └──────────────────┴─────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────┐ │
|
|
│ │ API Request │ │
|
|
│ │ with projectId │ │
|
|
│ └────────┬────────┘ │
|
|
└─────────────────────────────┼───────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ BACKEND LAYER │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ Projects │ │ Variables │ │ Rules (Enh.) │ │
|
|
│ │ Controller │ │ Controller │ │ Controller │ │
|
|
│ ├──────────────────┤ ├──────────────────┤ ├──────────────────┤ │
|
|
│ │ GET /projects │ │ GET /vars │ │ GET /rules │ │
|
|
│ │ POST /projects │ │ POST /vars │ │ ?project=... │ │
|
|
│ │ PUT /projects │ │ PUT /vars │ │ │ │
|
|
│ │ DELETE /projects │ │ DELETE /vars │ │ Returns: │ │
|
|
│ └────────┬─────────┘ └────────┬─────────┘ │ • text │ │
|
|
│ │ │ │ • renderedText │ │
|
|
│ │ │ │ • variables │ │
|
|
│ │ │ └────────┬─────────┘ │
|
|
│ │ │ │ │
|
|
│ ▼ ▼ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────┐ │
|
|
│ │ VARIABLE SUBSTITUTION SERVICE │ │
|
|
│ ├─────────────────────────────────────────────────────────────┤ │
|
|
│ │ │ │
|
|
│ │ substituteVariables(ruleText, projectId) │ │
|
|
│ │ ┌───────────────────────────────────────────────────┐ │ │
|
|
│ │ │ 1. Extract ${VAR} from text │ │ │
|
|
│ │ │ 2. Query VariableValue for projectId │ │ │
|
|
│ │ │ 3. Build substitution map: {VAR: value} │ │ │
|
|
│ │ │ 4. Replace ${VAR} with actual value │ │ │
|
|
│ │ │ 5. Return renderedText + metadata │ │ │
|
|
│ │ └───────────────────────────────────────────────────┘ │ │
|
|
│ │ │ │
|
|
│ │ extractVariables(text) → ['VAR1', 'VAR2'] │ │
|
|
│ │ getAllVariables() → [{name, usageCount}] │ │
|
|
│ │ validateProjectVariables(projectId) → {missing} │ │
|
|
│ │ │ │
|
|
│ └─────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────┬───────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ DATABASE LAYER (MongoDB) │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ projects │ │ variableValues │ │ governanceRules │ │
|
|
│ ├──────────────────┤ ├──────────────────┤ ├──────────────────┤ │
|
|
│ │ id (PK) │ │ projectId (FK) │ │ id │ │
|
|
│ │ name │ │ variableName │ │ text (template) │ │
|
|
│ │ description │ │ value │ │ scope │ │
|
|
│ │ techStack │ │ description │ │ variables[] │ │
|
|
│ │ active │ │ category │ │ applicableProjs │ │
|
|
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
|
|
│ │
|
|
│ Index: (id) Index: (projectId, variableName) │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Data Flow: Variable Substitution
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ VARIABLE SUBSTITUTION FLOW │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
STEP 1: User Selects Project
|
|
┌────────────────┐
|
|
│ User clicks │
|
|
│ "Tractatus" in │
|
|
│ project picker │
|
|
└───────┬────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────────────┐
|
|
│ localStorage.setItem( │
|
|
│ 'currentProject', │
|
|
│ 'tractatus' │
|
|
│ ) │
|
|
└───────┬────────────────────────┘
|
|
│
|
|
▼
|
|
|
|
STEP 2: Frontend Requests Rules with Context
|
|
┌────────────────────────────────┐
|
|
│ GET /api/admin/rules? │
|
|
│ project=tractatus │
|
|
└───────┬────────────────────────┘
|
|
│
|
|
▼
|
|
|
|
STEP 3: Backend Filters Applicable Rules
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Query: │
|
|
│ { │
|
|
│ $or: [ │
|
|
│ { scope: 'UNIVERSAL' }, │
|
|
│ { applicableProjects: 'tractatus' } │
|
|
│ ], │
|
|
│ active: true │
|
|
│ } │
|
|
└───────┬─────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
|
|
STEP 4: For Each Rule, Extract Variables
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Rule Text: │
|
|
│ "MongoDB database MUST be named ${DB_NAME} │
|
|
│ in ${ENVIRONMENT}." │
|
|
│ │
|
|
│ Extracted Variables: │
|
|
│ ['DB_NAME', 'ENVIRONMENT'] │
|
|
└───────┬─────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
|
|
STEP 5: Lookup Variable Values for Project
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Query variableValues: │
|
|
│ { │
|
|
│ projectId: 'tractatus', │
|
|
│ variableName: { │
|
|
│ $in: ['DB_NAME', 'ENVIRONMENT'] │
|
|
│ } │
|
|
│ } │
|
|
│ │
|
|
│ Results: │
|
|
│ [ │
|
|
│ {variableName: 'DB_NAME', value: 'tractatus_dev'}, │
|
|
│ {variableName: 'ENVIRONMENT', value: 'development'} │
|
|
│ ] │
|
|
└───────┬─────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
|
|
STEP 6: Build Substitution Map
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Substitution Map: │
|
|
│ { │
|
|
│ 'DB_NAME': 'tractatus_dev', │
|
|
│ 'ENVIRONMENT': 'development' │
|
|
│ } │
|
|
└───────┬─────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
|
|
STEP 7: Replace Placeholders
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Original: │
|
|
│ "MongoDB database MUST be named ${DB_NAME} │
|
|
│ in ${ENVIRONMENT}." │
|
|
│ │
|
|
│ Regex: /\$\{([A-Z_]+)\}/g │
|
|
│ │
|
|
│ Rendered: │
|
|
│ "MongoDB database MUST be named tractatus_dev │
|
|
│ in development." │
|
|
└───────┬─────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
|
|
STEP 8: Return Enriched Response
|
|
┌─────────────────────────────────────────────────┐
|
|
│ { │
|
|
│ "id": "inst_019", │
|
|
│ "text": "...${DB_NAME}...${ENVIRONMENT}...", │
|
|
│ "renderedText": "...tractatus_dev...development...", │
|
|
│ "variables": [ │
|
|
│ {"name": "DB_NAME", "value": "tractatus_dev"}, │
|
|
│ {"name": "ENVIRONMENT", "value": "development"} │
|
|
│ ], │
|
|
│ "scope": "UNIVERSAL" │
|
|
│ } │
|
|
└───────┬─────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
|
|
STEP 9: Frontend Displays
|
|
┌─────────────────────────────────────────────────┐
|
|
│ ┌─────────────────────────────────────────────┐ │
|
|
│ │ inst_019 | SYSTEM | HIGH │ │
|
|
│ │ │ │
|
|
│ │ Rendered (for Tractatus): │ │
|
|
│ │ MongoDB database MUST be named tractatus_dev│ │
|
|
│ │ in development. │ │
|
|
│ │ │ │
|
|
│ │ Template: ...${DB_NAME}...${ENVIRONMENT}... │ │
|
|
│ │ Variables: DB_NAME=tractatus_dev, │ │
|
|
│ │ ENVIRONMENT=development │ │
|
|
│ └─────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## UI Component Hierarchy
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ ADMIN INTERFACE │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ Navbar (All Pages) │
|
|
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Tractatus Admin [Project: Tractatus ▼] Dashboard Rules [Logout]│ │
|
|
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
┌───────────────┼───────────────┐
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌────────────────┐ ┌───────────────┐ ┌─────────────┐
|
|
│ Rule Manager │ │ Project │ │ Dashboard │
|
|
│ │ │ Manager │ │ │
|
|
├────────────────┤ ├───────────────┤ └─────────────┘
|
|
│ • List Rules │ │ • List Projs │
|
|
│ • Rendered │ │ • Add Project │
|
|
│ Text │ │ • Edit Proj │
|
|
│ • Template │ │ │
|
|
│ • Variables │ │ ┌─────────┐ │
|
|
│ • Edit Rule │ │ │ Project │ │
|
|
│ │ │ │ Editor │ │
|
|
│ ┌──────────┐ │ │ ├─────────┤ │
|
|
│ │ Variable │ │ │ │ • Meta │ │
|
|
│ │ Editor │ │ │ │ • Vars │ │
|
|
│ │ Modal │ │ │ │ │ │
|
|
│ └──────────┘ │ │ │ ┌─────┐ │ │
|
|
└────────────────┘ │ │ │ Var │ │ │
|
|
│ │ │ Edit│ │ │
|
|
│ │ └─────┘ │ │
|
|
│ └─────────┘ │
|
|
└───────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Component Interaction Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ COMPONENT INTERACTIONS │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
Event Flow: User Changes Project Selection
|
|
|
|
┌─────────────────┐
|
|
│ ProjectSelector │ (1) User clicks project dropdown
|
|
└────────┬────────┘
|
|
│
|
|
│ (2) Emits 'projectChanged' event
|
|
│ { projectId: 'tractatus' }
|
|
│
|
|
├──────────────────────┬───────────────────┐
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌────────────────┐ ┌──────────────────┐ ┌─────────────┐
|
|
│ Rule Manager │ │ Project Manager │ │ Dashboard │
|
|
└────────┬───────┘ └────────┬─────────┘ └──────┬──────┘
|
|
│ │ │
|
|
│ (3) Listens │ (3) Listens │ (3) Listens
|
|
│ to event │ to event │ to event
|
|
│ │ │
|
|
│ (4) Reloads │ (4) Updates │ (4) Updates
|
|
│ rules with │ current │ stats for
|
|
│ new context │ selection │ project
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ localStorage.setItem('currentProject', id) │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Database Relationships
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ DATABASE RELATIONSHIPS │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────────┐
|
|
│ projects │
|
|
│ │
|
|
│ id (PK) ─────────┼──────────┐
|
|
│ name │ │
|
|
│ techStack │ │ 1:N
|
|
│ active │ │
|
|
└──────────────────┘ │
|
|
│
|
|
▼
|
|
┌──────────────────┐
|
|
│ variableValues │
|
|
│ │
|
|
│ projectId (FK) ──┼─── References projects.id
|
|
│ variableName │
|
|
│ value │
|
|
│ │
|
|
│ UNIQUE(projectId,│
|
|
│ variableName) │
|
|
└──────────────────┘
|
|
│
|
|
│ Referenced by
|
|
│ (through variables[] field)
|
|
│
|
|
▼
|
|
┌──────────────────┐
|
|
│ governanceRules │
|
|
│ │
|
|
│ id │
|
|
│ text (template) │
|
|
│ variables[] ─────┼─── e.g., ['DB_NAME', 'DB_PORT']
|
|
│ scope │
|
|
│ applicableProj[] ┼─── e.g., ['tractatus', 'family-history']
|
|
└──────────────────┘
|
|
|
|
Example Query Flow:
|
|
1. Get rules for project "tractatus"
|
|
2. Find rules where scope=UNIVERSAL OR 'tractatus' in applicableProjects
|
|
3. For each rule, extract variables[] field
|
|
4. Query variableValues where projectId='tractatus' AND variableName IN variables[]
|
|
5. Substitute ${VAR} with values
|
|
6. Return enriched rules
|
|
```
|
|
|
|
---
|
|
|
|
## API Endpoints Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ API ENDPOINTS (Phase 3) │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
PROJECTS API
|
|
├─ GET /api/admin/projects → List all projects
|
|
├─ GET /api/admin/projects/:id → Get project + variables
|
|
├─ POST /api/admin/projects → Create project
|
|
├─ PUT /api/admin/projects/:id → Update project
|
|
└─ DELETE /api/admin/projects/:id → Soft delete project
|
|
|
|
VARIABLES API
|
|
├─ GET /api/admin/projects/:id/variables → Get all vars for project
|
|
├─ POST /api/admin/projects/:id/variables → Create/update variable
|
|
├─ PUT /api/admin/projects/:id/variables/:var → Update variable value
|
|
├─ DELETE /api/admin/projects/:id/variables/:var → Delete variable
|
|
└─ GET /api/admin/variables/global → All unique variable names
|
|
|
|
RULES API (Enhanced)
|
|
└─ GET /api/admin/rules?project=:id → Get rules with substitution
|
|
(Enhanced existing endpoint)
|
|
```
|
|
|
|
---
|
|
|
|
## Substitution Algorithm Pseudocode
|
|
|
|
```javascript
|
|
function substituteVariables(ruleText, projectId) {
|
|
// Step 1: Extract variable placeholders
|
|
const regex = /\$\{([A-Z_]+)\}/g;
|
|
const variableNames = [...new Set(
|
|
[...ruleText.matchAll(regex)].map(m => m[1])
|
|
)];
|
|
|
|
if (variableNames.length === 0) {
|
|
return {
|
|
renderedText: ruleText,
|
|
variables: []
|
|
};
|
|
}
|
|
|
|
// Step 2: Query database for values
|
|
const values = await VariableValue.find({
|
|
projectId: projectId,
|
|
variableName: { $in: variableNames }
|
|
});
|
|
|
|
// Step 3: Build substitution map
|
|
const substitutionMap = {};
|
|
values.forEach(v => {
|
|
substitutionMap[v.variableName] = v.value;
|
|
});
|
|
|
|
// Step 4: Replace placeholders
|
|
const renderedText = ruleText.replace(
|
|
regex,
|
|
(match, varName) => substitutionMap[varName] || match
|
|
);
|
|
|
|
// Step 5: Build metadata
|
|
const variables = variableNames.map(name => ({
|
|
name: name,
|
|
value: substitutionMap[name] || null,
|
|
missing: !substitutionMap[name]
|
|
}));
|
|
|
|
return {
|
|
renderedText,
|
|
variables,
|
|
hasAllValues: variables.every(v => !v.missing)
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
**Architecture Complexity**: Medium
|
|
**Integration Points**: 3 (Models, Services, Controllers)
|
|
**New Collections**: 2 (projects, variableValues)
|
|
**Enhanced Collections**: 1 (governanceRules - query logic only)
|
|
|
|
---
|
|
|
|
**Diagram Created**: 2025-10-11
|
|
**For**: Phase 3 Implementation
|
|
**Status**: Ready for Development
|