# Technical Architecture: Obsidian-Nuxt Integration

**For:** Technical team members, maintainers

---

## System Overview

```
Obsidian Vault (source)           Nuxt Content (processing)         Web (output)
    ↓                                   ↓                              ↓
content/articles/*.md      →  Wikilink Transform Plugin  →  /articles/slug
  with [[links]]              (Nitro server plugin)            with /articles/ links
  with ![[images]]                                              with /img/ references
                               Image embed transformation
                               (Nitro server plugin)

                           ↓

                      Nuxt build generates
                      Static HTML + JSON
```

---

## Architecture Components

### 1. Obsidian Vault Configuration

**Location:** `/content/articles/.obsidian/`

**Key Files:**
- `app.json` - Settings (attachment folder, link format, etc.)
- `community-plugins.json` - List of plugins to enable
- `hotkeys.json` - Keyboard shortcuts
- `plugins/obsidian-git/data.json` - Git plugin configuration

**Attachment Folder:**
```json
{
  "attachmentFolderPath": "../../public/img"
}
```

This tells Obsidian to save images to `/public/img/` instead of keeping them in the vault.

**User-Specific (Gitignored):**
- `workspace.json` - Open files, window layout (per-user)
- `appearance.json` - Theme preferences (per-user)
- `cache/` - Obsidian's internal cache
- `graph.json` - Graph view state

**Shared (Committed):**
- `app.json` - Core settings
- `community-plugins.json` - Plugin list
- `hotkeys.json` - Team shortcuts

---

### 2. Wikilink Transformation Plugin

**Location:** `/app/server/plugins/wikilink-transform.ts`

**Hook:** `content:file:beforeParse`

**Timing:** Runs during Nuxt Content processing, before markdown parsing

**Transformations:**

#### Wikilinks → Markdown Links

```typescript
// Input
[[P0 - Communication Norms for Game Studios]]
[[Grant Writing Guide|Grant Guide]]

// Output
[Communication Norms for Game Studios](/articles/communication-norms-for-game-studios)
[Grant Guide](/articles/grant-writing-guide)

// Logic
1. Strip P0/P1/P2 prefix
2. Convert title to slug (lowercase, spaces→hyphens, special chars→removed)
3. Create markdown link with /articles/ prefix
```

**Slug Generation:**
```typescript
function titleToSlug(title: string): string {
  return title
    .toLowerCase()
    .replace(/['"]/g, '')              // Remove quotes
    .replace(/&/g, 'and')              // & → and
    .replace(/[^a-z0-9\s-]/g, '')    // Remove special chars
    .replace(/\s+/g, '-')              // Spaces → hyphens
    .replace(/-+/g, '-')               // Collapse hyphens
    .replace(/^-|-$/g, '');            // Trim edges
}

// Examples:
"Grant Writing & Funding Strategy" → "grant-writing-and-funding-strategy"
"Co-op: Structure & Governance" → "co-op-structure-and-governance"
"Q&A: Funding" → "qa-funding"
```

#### Image Embeds → Markdown Images

```typescript
// Input
![[diagram.jpg]]
![[workflow.png|Team workflow diagram]]

// Output
![diagram.jpg](/img/diagram.jpg)
![Team workflow diagram](/img/workflow.png)

// Logic
1. Extract filename and optional alt text
2. Create markdown image with /img/ prefix
```

---

### 3. Content Processing Pipeline

**Sequence:**

```
1. File read from disk
   File: /content/articles/article-name.md

   ↓

2. content:file:beforeParse hook (OUR PLUGIN)
   - Extract wikilinks: [[...]]
   - Extract image embeds: ![[...]]
   - Transform both to markdown syntax
   - Update file.body

   ↓

3. Frontmatter parsing (gray-matter)
   - Extract YAML frontmatter
   - Validate against schema

   ↓

4. Markdown parsing (Nuxt Content)
   - Convert markdown to AST
   - Generate HTML structure

   ↓

5. Static generation
   - Render article pages
   - Create JSON payloads
   - Output to .output/
```

**No Markdown Plugins Needed:**

Nuxt Content v3 uses Nuxt's built-in markdown processor. We don't need remark/rehype plugins because the Nitro plugin hook runs before all parsing.

---

### 4. Image Handling

**Storage:** `/public/img/`

**Workflow:**

```
User in Obsidian
    ↓
Pastes image (Cmd+V)
    ↓
Obsidian saves to /public/img/
    ↓
User writes ![[filename.jpg]]
    ↓
Git add + commit
    ↓
Image goes to repository
    ↓
At build:
  - Plugin transforms ![[filename.jpg]] → ![...](/img/filename.jpg)
  - Build copies /public/img/ to output
    ↓
Web server serves /img/filename.jpg
```

**Path Resolution:**

When Obsidian saves image from vault at `/content/articles/`:
```
From:     /content/articles/some-article.md
To:       /public/img/
Relative: ../../public/img

So when you paste in Obsidian, it:
  Saves to:  /public/img/
  Creates:   ![image](../../public/img/image.jpg)
```

The transformation plugin converts this to `/img/image.jpg` for web output.

---

### 5. Nuxt Content Collection

**Configuration:** `content.config.ts`

```typescript
export default defineContentConfig({
  collections: {
    articles: defineCollection({
      type: "data",
      source: "**/*.md",  // Finds all .md files
      schema: z.object({
        title: z.string(),
        description: z.string().optional(),
        category: z.string().optional(),
        tags: z.array(z.string()).optional(),
        author: z.string().optional(),
        contributors: z.array(z.string()).optional(),
        published: z.boolean().default(true),
        featured: z.boolean().default(false),
        accessLevel: z.string().optional(),
        publishedAt: z.string().optional(),
      }),
    }),
  },
});
```

**Validation:**
- Frontmatter must match schema
- Missing required fields cause build errors
- All fields run through Zod validation

---

### 6. Git Workflow Integration

**Via Obsidian Git Plugin**

**Configuration:** `.obsidian/plugins/obsidian-git/data.json`

```json
{
  "autoPullOnBoot": true,         // Pull on startup
  "autoPullInterval": 5,           // Pull every 5 minutes
  "disablePush": false,            // Allow pushing
  "pullBeforePush": true,          // Always pull before pushing
  "conflictHandlingStrategy": "ask" // Ask on conflicts
}
```

**User Workflow:**

1. **User A** edits article, commits, pushes
2. **User B** starts Obsidian → auto-pulls User A's changes
3. **User B** edits different article → no conflict
4. **User B** commits & pushes
5. **User A** continues working → might pull when prompted

**Conflict Resolution:**

If both users edit same file:
```
1. Pull fails with conflict
2. Obsidian Git creates conflict-files-obsidian-git.md
3. User sees conflict markers in file:
   <<<<<<< HEAD
   User A's version
   =======
   User B's version
   >>>>>>> main
4. User manually resolves (chooses/merges versions)
5. User commits resolution
6. User pushes
```

---

## Build Process

### Development (`npm run dev`)

```bash
# Starts:
# 1. Nuxt dev server (hot reload)
# 2. Watches /content/articles/ for changes
# 3. When file saves: re-processes & hot-reload in browser

npm run dev
# → http://localhost:3000
# → Changes live-update without refresh
```

### Production (`npm run generate`)

```bash
# Builds static site:
# 1. Processes all content files
# 2. Runs wikilink transformation
# 3. Generates HTML pages + JSON
# 4. Optimizes assets
# 5. Outputs to .output/public/

npm run generate
# → .output/public/ contains complete static site
# → Ready to deploy to CDN/hosting
```

### Static Preview (`npm run preview`)

```bash
# Starts server serving built output:
# Shows what production will look like

npm run preview
# → http://localhost:3000
# → Serves from .output/public/
```

---

## File Mappings

**Article Files → URL Slugs:**

```
Obsidian filename               Content title                    URL slug
────────────────────────────────────────────────────────────────────────
communication-norms*.md         Communication Norms Document     /articles/communication-norms-document-for-game-studios
meeting-agenda*.md              Meeting Agenda & Facilitation    /articles/meeting-agenda-facilitation-guide-for-game-studios
co-op-structure*.md             Co-op: Structure & Governance    /articles/co-op-structure-and-governance

*Actual filename doesn't matter for slug generation
Slug comes from frontmatter title
```

**Why this matters:**

If you rename a file in Obsidian:
- ❌ Obsidian updates internal links automatically (good!)
- ✅ Slug stays same (derived from title, not filename)
- If you change the title in frontmatter: slug changes → URL changes

---

## Performance Characteristics

### Build Times

| Operation | Time | Notes |
|-----------|------|-------|
| Dev server startup | ~10-15s | Nuxt init + content processing |
| Hot reload | ~1-2s | Single file change |
| Full static build | ~30-60s | 42 articles + optimization |
| Content indexing | ~100ms | Build-time caching |

### Runtime Performance

- **No runtime transformation** - All done at build
- **Static HTML output** - Zero processing at serve time
- **Fast first page load** - No client-side rendering for content

### Scaling

- **100 articles** - Build still <60s
- **1000 articles** - Might need optimization (parallel builds)
- **Image count** - No impact (images in /public/, not in content)

---

## Edge Cases & Solutions

### Case Sensitivity

**Problem:** Filenames case-sensitive on Linux, case-insensitive on Mac

**Solution:** Always use lowercase slugs
```yaml
# ✅ Good
- [[Grant Writing Guide]]
- [[case-study-studio-xyz]]

# ❌ Problematic
- [[Grant WRITING Guide]]
- [[Case-Study-Studio-XYZ]]
```

### Special Characters

**In Article Titles:**
```
Input:  "Co-op: Structure & Governance"
Slug:   "co-op-structure-and-governance"

Input:  "Q&A: Funding"
Slug:   "qa-funding"

Input:  "The "Beginning""
Slug:   "the-beginning"
```

**In Image Names:**
```
✅ Good: meeting-workflow-diagram.jpg
❌ Bad:  Meeting Workflow Diagram.jpg (spaces)
❌ Bad:  image (2).jpg (parentheses)
```

### Circular References

**Not a problem:**
- Article A links to Article B
- Article B links to Article A
- Both work fine (static links, no runtime resolution)

### Non-Existent Links

**Behavior:**
- Wikilink transforms to `/articles/non-existent-page`
- User clicks → sees 404 page
- No build error

**Detection (optional enhancement):**
```typescript
// Could validate at build time:
// - Get all article slugs
// - Check each transformed link
// - Warn if any don't exist
```

---

## Troubleshooting

### Build Fails: "Invalid frontmatter"

**Cause:** File violates schema

**Fix:**
```yaml
# Check:
- Is 'title' present?
- Is YAML syntax valid (spaces not tabs)?
- Are arrays formatted correctly?

# Example invalid:
- tags: baby-ghosts, p0  # ❌ Missing brackets
+ tags: [baby-ghosts, p0]  # ✅ Correct array syntax
```

### Wikilinks Showing Literally

**Cause:** Plugin didn't run or had error

**Check:**
```bash
# 1. Restart dev server
npm run dev

# 2. Check plugin file exists
ls app/server/plugins/wikilink-transform.ts

# 3. Check for errors in terminal
# Look for "error TS" messages

# 4. Clear cache
rm -rf .nuxt
npm run dev
```

### Images Not Displaying

**Cause 1:** Image not in `/public/img/`

```bash
# Check:
ls public/img/image.jpg

# If missing, add it:
cp path/to/image.jpg public/img/
git add public/img/image.jpg
```

**Cause 2:** Wrong path in markdown

```markdown
# Check these:
![[image.jpg]]              # ✅ Correct (Obsidian syntax)
![](/img/image.jpg)        # ✅ Correct (markdown)
![](/public/img/image.jpg) # ❌ Wrong (extra /public/)
```

### Git Conflicts

**Prevention:**
- Pull before editing
- Work on different files
- Push frequently

**Resolution:**
- See OBSIDIAN_SETUP_GUIDE.md → "Handling Conflicts"

---

## Future Enhancements

### Backlinks Display

Show which articles link to current article:

```vue
<div class="backlinks">
  <h3>Referenced by:</h3>
  <ul>
    <li v-for="article in backlinks">
      <NuxtLink :to="`/articles/${article.slug}`">
        {{ article.title }}
      </NuxtLink>
    </li>
  </ul>
</div>
```

### Link Validation at Build

Fail build if broken wikilinks detected:

```typescript
// In plugin:
const allSlugs = /* get all article slugs */
wikilinks.forEach(link => {
  const slug = wikilinkToSlug(link)
  if (!allSlugs.includes(slug)) {
    throw new Error(`Broken wikilink: ${link}`)
  }
})
```

### Graph Visualization

Show article connections as interactive graph:

```
Communication Norms
      ↓
   /  |  \
  ↓   ↓   ↓
M.Guide  Peer Support  Bylaws
```

---

## Maintenance

### Regular Tasks

**Weekly:**
- Check for broken images (audit script)
- Review commit messages (consistency)

**Monthly:**
- Run full build test (`npm run generate`)
- Test on staging server
- Backup Forgejo repository

**Quarterly:**
- Review plugin versions (Obsidian Git, etc.)
- Update dependencies if needed

### Updating Obsidian Vault Config

Only need to commit `.obsidian/` folder if config changes:

```bash
# Check what changed
git status

# If app.json changed:
git add content/articles/.obsidian/app.json
git commit -m "Update Obsidian vault settings"
git push

# Other users pull and get new settings
```

---

## References

- **Nuxt Content v3:** https://content.nuxt.com
- **Nuxt:** https://nuxt.com
- **Nitro:** https://nitro.unjs.io
- **Obsidian:** https://obsidian.md
- **Obsidian Git:** https://github.com/Vinzent03/obsidian-git

---

**Last Updated:** November 2025

**Maintainer:** Wiki-GhostGuild Team
