The standard advice for senior engineers is “be more strategic.” Nobody provides tooling for what that actually means in practice.

Most productivity and knowledge management tools are built for managers or PMs. The senior IC workflow — navigating informal power structures, tracking skill bets across years, building visibility without a title change — doesn’t fit any standard category. Notion, Confluence, JIRA: these are collaboration tools optimized for teams, not personal career intelligence.

I built VividMap to fill that gap. Here’s the technical breakdown.

The Stack

Vue.js 3 (Composition API) + Pinia + Vue Router
.NET 10 Minimal API
PostgreSQL 17
Firebase Authentication (manual JWT verification)
Docker Compose deployment on VPS

Deliberately conventional. VividMap’s value is in the domain modeling, not the infrastructure. C# is what I know deeply; the runtime doesn’t affect the product.

Domain Model: What “Career Intelligence” Actually Means in Data

The eight modules map to distinct data types:

ModuleCore Data Type
Shadow Org ChartGraph (nodes + weighted edges with influence type)
Maps (You Are Here, Topographical, Treasure)Hierarchical position + goal paths
Skill Learning TrackerSkills with multi-dimensional cost estimates
Technology RoadmapTimeline items with dependency edges
Second BrainHierarchical folders + full-text searchable notes
Pomodoro TimerSessions linked to initiatives (not generic time)
Engineering LoopAnalytics integration (PostHog)
AI AssistantRetrieval over all user data + LLM completion

The most interesting data structure is the org chart.

The Shadow Org Chart as a Graph

The official hierarchy is a tree. The shadow org chart is a directed graph.

Each node is a person. Each edge has:

  • influenceType: ALLY | BLOCKER | NEUTRAL | SPONSOR | MENTEE
  • weight: float representing influence strength (0.0–1.0)
  • notes: private text about why this person matters
  • isInformal: whether the edge is in the official org chart or the shadow map
public class OrgNode
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string? Title { get; set; }
    public string? Team { get; set; }
    public bool IsInformal { get; set; }
    public ICollection<OrgEdge> OutboundEdges { get; set; }
}

public class OrgEdge
{
    public Guid FromNodeId { get; set; }
    public Guid ToNodeId { get; set; }
    public InfluenceType InfluenceType { get; set; }
    public float Weight { get; set; }
    public string? Notes { get; set; }
    public bool IsInformal { get; set; }
}

PostgreSQL handles the persistence. The graph algorithms (shortest path to a decision-maker, influence reachability) run in the API layer using a standard adjacency list walk — the graphs are small enough (personal org chart, rarely more than 200 nodes) that a graph database adds no value.

The Vue.js frontend renders the graph using a canvas-based layout with force simulation. Informal edges render as dashed lines. Influence type maps to edge color.

Firebase Auth With Manual JWT Verification

This was the most surprising detour in the build.

Firebase Authentication normally works via service account key files — you download a JSON file from Google Cloud Console and pass it to the Firebase Admin SDK. The SDK verifies tokens server-side using the service account.

The problem: an org policy on the Google Cloud project blocked service account key generation. The policy that prevents it is a good security practice, but it meant the standard Firebase Admin SDK setup was unavailable.

The workaround: verify Firebase ID tokens manually using Google’s public JWKS endpoint.

public async Task<ClaimsPrincipal?> ValidateFirebaseTokenAsync(string idToken)
{
    // Google's public key endpoint for Firebase
    const string jwksUri = "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com";

    var jwks = await _httpClient.GetFromJsonAsync<JsonWebKeySet>(jwksUri);
    var tokenHandler = new JsonWebTokenHandler();

    var validationParams = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKeys = jwks!.GetSigningKeys(),
        ValidateIssuer = true,
        ValidIssuer = $"https://securetoken.google.com/{_projectId}",
        ValidateAudience = true,
        ValidAudience = _projectId,
        ValidateLifetime = true,
        ClockSkew = TimeSpan.FromMinutes(5)
    };

    var result = await tokenHandler.ValidateTokenAsync(idToken, validationParams);
    return result.IsValid ? new ClaimsPrincipal(result.ClaimsIdentity) : null;
}

JWKS keys rotate. The implementation caches them with the Cache-Control: max-age from the response headers and re-fetches when the cache expires — same behavior the Admin SDK produces, just without the service account dependency.

The AI Assistant: Context Is the Differentiator

The AI assistant is the feature that’s hardest to replicate with a generic AI chat tool.

The assistant has access to:

  • Your org chart (who you’ve identified as allies, blockers, sponsors)
  • Your Second Brain notes (1:1 summaries, architecture decisions, political context)
  • Your skill state and cost estimates
  • Your roadmap and initiative status

When you ask “I’m getting deprioritized in the upcoming reorg — what should I do?”, it responds with context about your specific org map, not generic reorg advice. That’s the differentiation: not a smarter model, but a model with actually relevant context.

The implementation is RAG over the user’s own data:

public async Task<string> GetAssistantResponseAsync(string userId, string query)
{
    // Retrieve relevant context chunks from user's data
    var orgContext = await _orgChartService.GetRelevantContextAsync(userId, query);
    var notesContext = await _secondBrainService.SearchAsync(userId, query, limit: 5);
    var skillContext = await _skillService.GetCurrentStateAsync(userId);

    var systemPrompt = BuildSystemPrompt(orgContext, notesContext, skillContext);

    return await _claudeClient.CompleteAsync(systemPrompt, query);
}

The system prompt includes a structured summary of the org chart, relevant note excerpts, and skill state. The model is Claude Sonnet — token costs are manageable at personal scale. The retrieval isn’t doing dense vector search; it’s keyword-based filtering over the user’s own small dataset, which is fast and doesn’t require an embedding store.

What I’d Do Differently

Firebase auth decision earlier. The JWKS manual verification wasn’t hard, but it ate a day and a half I didn’t budget for. If the production environment has org policy restrictions, discover them in week one, not week twelve.

Graph storage. I’m storing the org chart in PostgreSQL as two standard tables (nodes + edges). For the current use case (personal org charts under 200 nodes), this is fine. If VividMap ever supports team-level org maps with thousands of nodes, a proper graph storage approach would matter. For now, the PostgreSQL representation is a complexity-appropriate choice.

The Pomodoro timer should have been last. It was easy to build and satisfying to ship, but the timer isn’t what differentiates the product. I spent time on it early when the graph visualization and AI assistant integration deserved that time more. Feature prioritization in solo products without a PM is harder than it looks.

Current Status

VividMap is live at app.vividmap.io. Free during beta.

If you’re a senior IC or staff engineer who thinks about the org navigation layer of the job, this is what I built. Happy to answer questions about the shadow org chart design, the Firebase workaround, or the RAG architecture.


VividMap is a career intelligence tool for Staff+ engineers. Live at app.vividmap.io — free during beta.