# API Reference

Complete reference for all functions, data structures, and interfaces in the SharePoint Permissions Visualizer.

## Data Structures

### State Object

```typescript
interface State {
  principals: Principal[]
  nodes: Node[]
  selectedId: string | null
  schemaVersion: number
  _flashAsg?: boolean  // Internal UI flag
}
```

### Principal

```typescript
interface Principal {
  id: string
  name: string
  kind: 'user' | 'group' | 'external' | 'link-virtual'
}
```

**Properties:**
- `id`: Unique identifier generated by `makeId()`
- `name`: Display name or email address
- `kind`:
  - `user`: Regular organizational user
  - `group`: SharePoint or Microsoft 365 group
  - `external`: Guest/external identity
  - `link-virtual`: Virtual principal for sharing links (internal use)

**Example:**
```javascript
{
  id: "x7k2p9",
  name: "Alice Johnson",
  kind: "user"
}
```

### Node

```typescript
interface Node {
  id: string
  type: 'site' | 'library' | 'folder' | 'file'
  name: string
  parentId: string | null
  inherits: boolean
  assignments: Assignment[]
  sharingLinks: SharingLink[]
}
```

**Properties:**
- `id`: Unique identifier
- `type`: Node type in hierarchy
- `name`: Display name
- `parentId`: Parent node ID, null for root site
- `inherits`: If true, inherits permissions from parent
- `assignments`: Direct permission grants on this node
- `sharingLinks`: Sharing links created on this node

**Structural Rules:**
- Only one root site (`type: 'site'`, `parentId: null`)
- Libraries can only be children of sites
- Folders can be children of libraries or folders
- Files can be children of libraries or folders
- Nothing can be a child of a file

**Example:**
```javascript
{
  id: "a1b2c3",
  type: "folder",
  name: "Marketing",
  parentId: "d4e5f6",
  inherits: false,
  assignments: [
    {principalId: "x7k2p9", role: "Edit"}
  ],
  sharingLinks: [
    {id: "link1", type: "org", role: "Read", created: 1234567890}
  ]
}
```

### Assignment

```typescript
interface Assignment {
  principalId: string
  role: Role
}
```

**Properties:**
- `principalId`: Reference to a principal in `app.state.principals`
- `role`: Permission level granted

### SharingLink

```typescript
interface SharingLink {
  id: string
  type: 'anyone' | 'org' | 'specific' | 'existing'
  role: Role
  created: number
}
```

**Properties:**
- `id`: Unique identifier
- `type`: Link access type
  - `anyone`: Anonymous access (no sign-in)
  - `org`: All organization members
  - `specific`: Specific named users
  - `existing`: No new permissions (just a URL)
- `role`: Permission level granted by link
- `created`: Timestamp (milliseconds since epoch)

### Role

```typescript
type Role = 'None' | 'Read' | 'Contribute' | 'Edit' | 'Full Control'
```

**Hierarchy (lowest to highest):**
```
None < Read < Contribute < Edit < Full Control
```

### EffectivePermission

```typescript
interface EffectivePermission {
  role: Role
  sources: PermissionSource[]
  isLink?: boolean           // True if from sharing link
  linkType?: LinkType        // Type of sharing link
  bypassesInheritance?: boolean  // True if link bypasses broken inheritance
}
```

**Properties:**
- `role`: Highest role across all sources
- `sources`: All locations granting access
- `isLink`: Flag indicating virtual principal from sharing link
- `linkType`: Type of sharing link (if `isLink` is true)
- `bypassesInheritance`: Flag for dangerous bypass behavior

### PermissionSource

```typescript
interface PermissionSource {
  nodeId: string
  role: Role
  type: 'assignment' | 'link'
  linkType?: LinkType        // If type is 'link'
  linkId?: string            // If type is 'link'
  bypassesInheritance?: boolean  // If link bypasses unique perms
}
```

**Properties:**
- `nodeId`: Node where access was granted
- `role`: Role granted at this source
- `type`: Assignment or sharing link
- `linkType`: Type of sharing link (if applicable)
- `linkId`: Link identifier (if applicable)
- `bypassesInheritance`: Flag for bypass behavior (if applicable)

### RiskAssessment

```typescript
interface RiskAssessment {
  level: 'Low' | 'Medium' | 'High'
  color: 'ok' | 'warn' | 'bad'
  msg: string
}
```

**Properties:**
- `level`: Risk severity
- `color`: CSS class for badge styling
- `msg`: Human-readable explanation

## Core Functions

### `makeId(): string`

**Lines:** 187

**Purpose:** Generate unique identifier

**Returns:** Random 8-character alphanumeric string

**Example:**
```javascript
const id = makeId()  // "x7k2p9m4"
```

**Implementation:**
```javascript
Math.random().toString(36).slice(2, 10)
```

---

### `clone(o: any): any`

**Lines:** 189

**Purpose:** Deep clone an object

**Parameters:**
- `o`: Object to clone

**Returns:** Deep copy of object

**Example:**
```javascript
const original = {nodes: [{id: 1}]}
const copy = clone(original)
copy.nodes[0].id = 2
// original.nodes[0].id is still 1
```

**Implementation:**
```javascript
JSON.parse(JSON.stringify(o))
```

**Warning:** Loses functions, undefined values, and circular references.

---

### `byId(id: string): Node | undefined`

**Lines:** 201

**Purpose:** Find node by ID

**Parameters:**
- `id`: Node identifier

**Returns:** Node object or undefined

**Example:**
```javascript
const node = byId("a1b2c3")
```

---

### `childrenOf(id: string): Node[]`

**Lines:** 202

**Purpose:** Get all children of a node

**Parameters:**
- `id`: Parent node identifier

**Returns:** Array of child nodes

**Example:**
```javascript
const kids = childrenOf(site.id)  // All libraries under site
```

---

### `pathOf(node: Node): string`

**Lines:** 203-206

**Purpose:** Generate full path string for a node

**Parameters:**
- `node`: Node object

**Returns:** Forward-slash separated path

**Example:**
```javascript
pathOf(file)  // "Contoso Marketing /Documents /Campaigns /Plan.docx"
```

**Algorithm:** Walk up tree collecting names, join with " /"

---

### `isExternal(pr: Principal): boolean`

**Lines:** 207

**Purpose:** Check if principal is external guest

**Parameters:**
- `pr`: Principal object

**Returns:** True if `kind === 'external'`

---

### `highestRole(a: Role, b: Role): Role`

**Lines:** 209-211

**Purpose:** Return higher of two roles

**Parameters:**
- `a`: First role
- `b`: Second role

**Returns:** Role with higher precedence

**Example:**
```javascript
highestRole('Read', 'Edit')  // 'Edit'
highestRole('Full Control', 'Contribute')  // 'Full Control'
```

**Implementation:** Uses `ROLE_ORDER` array index comparison

---

### `computeEffectiveDetailed(node: Node): Record<string, EffectivePermission>`

**Lines:** 214-263

**Purpose:** Calculate effective permissions for a node

**Parameters:**
- `node`: Node to analyze

**Returns:** Map of `principalId → EffectivePermission`

**Algorithm:**

**Phase 1: Direct Assignments (respects broken inheritance)**
```
cur = node
while cur exists:
  for each assignment in cur.assignments:
    add to accumulator (merge if exists)
  if cur.inherits is false:
    break  // Stop at broken inheritance
  cur = cur.parent
```

**Phase 2: Sharing Links (BYPASSES broken inheritance)**
```
cur = node
inheritanceWasBroken = false
while cur exists:
  for each link in cur.sharingLinks:
    if link.type != 'existing':
      create virtual principal "link-{type}-{nodeId}"
      bypassesInheritance = (inheritanceWasBroken && cur != node)
      add to accumulator with bypass flag
  if cur.inherits is false and cur != node:
    inheritanceWasBroken = true
  cur = cur.parent  // ALWAYS continue to root!
```

**Example:**
```javascript
const eff = computeEffectiveDetailed(file)
// {
//   "x7k2p9": {
//     role: "Edit",
//     sources: [
//       {nodeId: "site1", role: "Edit", type: "assignment"}
//     ]
//   },
//   "link-anyone-folder1": {
//     role: "Read",
//     sources: [
//       {nodeId: "folder1", role: "Read", type: "link", linkType: "anyone", bypassesInheritance: true}
//     ],
//     isLink: true,
//     linkType: "anyone",
//     bypassesInheritance: true
//   }
// }
```

**Time Complexity:** O(h × p) where h = tree height, p = avg principals per node

**Critical Behavior:** Sharing links ALWAYS walk to root, even through broken inheritance. This models dangerous real-world SharePoint behavior.

---

### `riskOf(node: Node): RiskAssessment`

**Lines:** 265-299

**Purpose:** Assess security risk for a node

**Parameters:**
- `node`: Node to analyze

**Returns:** Risk assessment object

**Algorithm:**
```
1. Calculate effective permissions
2. Walk to root checking for "Anyone" links
3. Check if any links bypass inheritance
4. Count editors, check for external users with Edit+
5. Return highest risk level found
```

**Risk Levels:**
- **High:**
  - "Anyone" sharing link exists (anonymous access)
  - Sharing link bypasses unique permissions
  - External identity has Edit or higher
- **Medium:**
  - Broken inheritance with 5+ editors
- **Low:**
  - No obvious risks

**Example:**
```javascript
const risk = riskOf(file)
// {
//   level: "High",
//   color: "bad",
//   msg: "\"Anyone\" sharing link grants anonymous access."
// }
```

**Time Complexity:** O(h + e) where h = tree height, e = effective principals

---

### `sample(): void`

**Lines:** 302-329

**Purpose:** Load pre-configured sample scenario

**Side Effects:** Replaces `app.state` with sample data

**Sample Structure:**
```
Contoso Marketing (Site)
└── Documents (Library)
    └── Campaigns (Folder - unique permissions)
        └── Q4 Launch (Folder)
            └── Plan.docx (File)
```

**Includes:**
- 7 principals (3 default site groups + company group + SP group + user + external)
- 5 nodes (site → library → 2 folders → file)
- Multiple permission assignments
- 2 sharing links (org link on library, anyone link on Q4 Launch)
- Broken inheritance on Campaigns folder

---

### `newModel(): void`

**Lines:** 331-347

**Purpose:** Create blank scenario with new site

**Side Effects:** Replaces `app.state` with minimal data

**Creates:**
- One site named "New Site"
- Three default groups (Owners, Members, Visitors)
- Default role assignments (Full Control, Edit, Read)

---

### `render(): void`

**Lines:** 350-354

**Purpose:** Trigger full UI re-render

**Side Effects:** Calls all three panel render functions

**Called After:** Every state mutation

**Pattern:**
```javascript
// Modify state
app.state.selectedId = newId

// Trigger render
render()
```

---

### `renderBuilder(): void`

**Lines:** 357-520

**Purpose:** Render left panel (build interface)

**Side Effects:**
- Updates `#builder` element innerHTML
- Binds event listeners to all interactive elements
- Applies flash animation if `app.state._flashAsg` is true

**Renders:**
- Selected item name and type
- Inheritance checkbox
- Add child/sibling buttons with context-aware options
- Delete button
- Grant access forms (direct assignment)
- Sharing links section with create/revoke
- Direct permissions list
- Principals directory

**Event Binding:**
- SELECT elements: `change` event only (allows dropdown to open)
- BUTTON elements: `click` event
- INPUT elements: `click`, `change`, and `input` events

---

### `actHandler(ev: Event): void`

**Lines:** 522-553

**Purpose:** Central event handler for user actions

**Parameters:**
- `ev`: DOM event from user interaction

**Dispatches to:**
- `rename`: Update node name
- `toggle-inherit`: Toggle inheritance flag
- `add-lib`, `add-folder`, `add-file`: Add child nodes
- `add-sibling-*`: Add sibling nodes
- `delete-node`: Delete node and subtree
- `set-role`: Change assignment role
- `del-assignment`: Remove assignment
- `add-principal`: Add to directory
- `del-principal`: Remove from directory
- `grant-existing`: Grant to existing principal
- `grant-new`: Add principal and grant
- `add-link`: Create sharing link
- `set-link-role`: Change link role
- `del-link`: Revoke sharing link

**Always ends with:** `render()` (except rename on keystroke)

---

### `renderTree(): void`

**Lines:** 665-684

**Purpose:** Render middle panel (tree structure)

**Side Effects:**
- Updates `#tree` element innerHTML
- Binds click handlers to all nodes

**Renders:**
- Recursive tree structure
- Icons: 🏠 (site), 📚 (library), 📁 (folder), 📄 (file)
- Badges: "inherits" (green) or "unique perms" (yellow)
- Sharing link indicator: 📤
- Selection highlight

**Interaction:** Clicking node sets `app.state.selectedId` and re-renders

---

### `renderEffects(): void`

**Lines:** 687-761

**Purpose:** Render right panel (effective permissions)

**Side Effects:**
- Updates `#effects` element innerHTML

**Renders:**
- Path and type of selected node
- Risk assessment badge and message
- Inheritance chain visualization
- Permission table with columns:
  - Person/Group
  - Type (User/Group/Guest/Link)
  - Permission (role)
  - Granted at (sources)
- Explanation text
- Warning box if links bypass inheritance

**Highlights:**
- Virtual principals displayed as link types ("Anyone with the link")
- Bypass warnings shown with red badge
- Explanation adapts based on inheritance status

---

### `addChild(parent: Node, type: NodeType): void`

**Lines:** 555-566

**Purpose:** Add child node to parent

**Parameters:**
- `parent`: Parent node object
- `type`: Type of child to add

**Side Effects:**
- Adds node to `app.state.nodes`
- Sets `app.state.selectedId` to new node

**Validation:**
- Cannot add under file
- Libraries only under sites
- Folders/files only under libraries or folders

**Naming:** Auto-generates name like "Library 1", "Folder 2", etc.

---

### `addSibling(node: Node, type: NodeType): void`

**Lines:** 568-578

**Purpose:** Add sibling node (same parent)

**Parameters:**
- `node`: Reference node
- `type`: Type of sibling to add

**Side Effects:**
- Adds node to `app.state.nodes` under same parent
- Sets `app.state.selectedId` to new node

**Validation:** Same rules as `addChild()` based on parent type

---

### `addSharingLink(): void`

**Lines:** 581-588

**Purpose:** Create sharing link on selected node

**Side Effects:**
- Adds link to `selected.sharingLinks`

**Reads from:**
- `#link-type` select element
- `#link-role` select element

**Defaults:** Type = 'org', Role = 'Read'

---

### `deleteNode(node: Node): void`

**Lines:** 590-595

**Purpose:** Delete node and all descendants

**Parameters:**
- `node`: Node to delete

**Side Effects:**
- Removes node and entire subtree from `app.state.nodes`
- Sets `app.state.selectedId` to parent

**Validation:** Cannot delete root site

**Uses:** `collectSubtree()` to find all descendants

---

### `collectSubtree(id: string): string[]`

**Lines:** 596-603

**Purpose:** Get all node IDs in subtree

**Parameters:**
- `id`: Root node ID

**Returns:** Array of node IDs (including root)

**Algorithm:** Depth-first traversal using stack

**Example:**
```javascript
const ids = collectSubtree(folder.id)
// ["folder1", "subfolder1", "file1", "file2", "subfolder2", "file3"]
```

---

### `assignPrincipalToSelected(pid: string, role: Role): void`

**Lines:** 619-626

**Purpose:** Grant permission to principal on selected node

**Parameters:**
- `pid`: Principal ID
- `role`: Role to grant

**Side Effects:**
- Adds or updates assignment in `selected.assignments`
- Sets `app.state._flashAsg = true` (triggers flash animation)

**Behavior:** If principal already assigned, updates role. Otherwise adds new assignment.

---

### `grantExisting(): void`

**Lines:** 628-633

**Purpose:** Grant permission to existing principal (from dropdown)

**Reads from:**
- `#grant-existing` select element
- `#grant-role` select element

**Delegates to:** `assignPrincipalToSelected()`

---

### `grantNew(): void`

**Lines:** 634-646

**Purpose:** Add new principal and grant permission

**Reads from:**
- `#grant-new-name` text input
- `#grant-new-kind` select element
- `#grant-new-role` select element

**Side Effects:**
- Adds principal to `app.state.principals`
- Grants permission on selected node
- Clears name input

**Delegates to:** `assignPrincipalToSelected()`

---

### `addPrincipalFromInputs(): void`

**Lines:** 648-654

**Purpose:** Add principal to directory (no permission grant)

**Reads from:**
- `#add-principal-name` text input
- `#add-principal-kind` select element

**Side Effects:**
- Adds principal to `app.state.principals`
- Clears name input

**Validation:** Requires non-empty name

---

### `removePrincipal(id: string): void`

**Lines:** 656-662

**Purpose:** Remove principal from directory

**Parameters:**
- `id`: Principal ID to remove

**Side Effects:**
- Removes principal from `app.state.principals`
- Removes all assignments referencing this principal

**Cascade:** Cleans up all references across all nodes

---

## Import/Export Functions

### `exportJSON(): void`

**Lines:** 764-767

**Purpose:** Download state as JSON file

**Side Effects:** Triggers browser download

**Filename:** `sp-permissions-scenario.json`

**Content:** `JSON.stringify(app.state)`

---

### `exportCSV(): void`

**Lines:** 768-786

**Purpose:** Download audit report as CSV

**Side Effects:** Triggers browser download

**Filename:** `sp-permissions-audit.csv`

**Columns:**
1. Path
2. Type
3. Inherits (Yes/No)
4. Risk (High/Medium/Low)
5. Risk Note
6. Unique Permissions (semicolon-separated)
7. Sharing Links (semicolon-separated)

**Rows:** One per node

**Example CSV:**
```csv
Path,Type,Inherits,Risk,Risk Note,Unique Permissions,Sharing Links
Contoso Marketing,site,Yes,Low,No obvious risks detected.,"All Company (Read); Marketing Members (Edit)",""
Contoso Marketing /Documents,library,Yes,Low,No obvious risks detected.,"","People in your organization (Read)"
```

---

### `csvEscape(s: string): string`

**Lines:** 787-789

**Purpose:** Escape string for CSV format

**Parameters:**
- `s`: String to escape

**Returns:** CSV-safe string

**Rules:**
- Wrap in quotes if contains: `,`, `"`, or newline
- Escape internal quotes by doubling: `"` → `""`

**Example:**
```javascript
csvEscape('Bob "The Builder" Jenkins')
// "Bob ""The Builder"" Jenkins"
```

---

### `downloadBlob(content: string, filename: string, type: string): void`

**Lines:** 791-797

**Purpose:** Trigger browser download of content

**Parameters:**
- `content`: File content
- `filename`: Download filename
- `type`: MIME type

**Implementation:**
1. Create Blob
2. Create temporary `<a>` element with blob URL
3. Programmatically click it
4. Revoke object URL after 1 second

---

### `importJSONFile(file: File): void`

**Lines:** 799-814

**Purpose:** Load scenario from JSON file

**Parameters:**
- `file`: File object from file input

**Side Effects:**
- Replaces `app.state` with file content
- Calls `render()`

**Validation:**
- Must be valid JSON
- Must have `nodes` array
- Must have `principals` array

**Migration:** Adds `sharingLinks: []` to nodes that lack it (v1 → v2)

**Error Handling:** Shows alert on invalid file

---

### `saveLocal(): void`

**Lines:** 816

**Purpose:** Save state to localStorage

**Key:** `sp_permissions_scenario`

**Side Effects:** Shows "Saved" alert

---

### `loadLocal(): void`

**Lines:** 817

**Purpose:** Load state from localStorage

**Side Effects:**
- Replaces `app.state` if data exists
- Calls `render()`
- Shows alert if no saved data

---

## Utility Functions

### `escapeHtml(s: string): string`

**Lines:** 820

**Purpose:** Escape HTML special characters

**Parameters:**
- `s`: String to escape

**Returns:** HTML-safe string

**Escapes:**
- `&` → `&amp;`
- `<` → `&lt;`
- `>` → `&gt;`

**Use:** Insert user content into HTML

**Example:**
```javascript
el.innerHTML = `<div>${escapeHtml(userName)}</div>`
```

---

### `escapeAttr(s: string): string`

**Lines:** 821

**Purpose:** Escape HTML attribute characters

**Parameters:**
- `s`: String to escape

**Returns:** Attribute-safe string

**Escapes:**
- `"` → `&quot;`
- `&` → `&amp;`
- `<` → `&lt;`
- `>` → `&gt;`

**Use:** Insert user content into HTML attributes

**Example:**
```javascript
el.innerHTML = `<input value="${escapeAttr(nodeValue)}"/>`
```

---

### `displayKind(k: string): string`

**Lines:** 822

**Purpose:** Convert principal kind to display label

**Parameters:**
- `k`: Principal kind

**Returns:** Display label

**Mapping:**
- `external` → `guest`
- All others → unchanged

---

### `typeLabel(t: string): string`

**Lines:** 579

**Purpose:** Convert node type to capitalized label

**Parameters:**
- `t`: Node type

**Returns:** Capitalized label

**Mapping:**
```javascript
{
  site: 'Site',
  library: 'Library',
  folder: 'Folder',
  file: 'File'
}
```

---

## Testing Functions

### `assert(cond: boolean, msg: string, results: AssertResult[]): void`

**Lines:** 847

**Purpose:** Add test assertion result

**Parameters:**
- `cond`: Condition to test
- `msg`: Description of test
- `results`: Array to collect results

**Side Effects:** Appends `{pass, msg}` to results array

---

### `runSelfTest(): void`

**Lines:** 848-877

**Purpose:** Execute automated test suite

**Side Effects:**
- Temporarily replaces state
- Runs test scenarios
- Shows alert with pass/fail count
- Logs results to console
- Restores original state

**Tests:**
1. Add principal
2. Grant Edit at library
3. Verify effective permissions
4. Add folder with broken inheritance
5. Verify inheritance break blocks access
6. Grant Contribute at folder
7. Add file
8. Verify child inherits from folder

**Pattern:**
```javascript
const snapshot = clone(app.state)
try {
  // Run tests
} catch(e) {
  alert('Self-test error: ' + e.message)
} finally {
  app.state = snapshot
  render()
}
```

---

## Event Handlers

### Toolbar Buttons

**Lines:** 825-834

```javascript
document.getElementById('btn-new').onclick = () => {newModel(); render()}
document.getElementById('btn-sample').onclick = () => {sample(); render()}
document.getElementById('btn-save').onclick = saveLocal
document.getElementById('btn-load').onclick = loadLocal
document.getElementById('btn-export-json').onclick = exportJSON
document.getElementById('btn-export-csv').onclick = exportCSV
document.getElementById('file-json').addEventListener('change', (e) => {
  if (e.target.files[0]) importJSONFile(e.target.files[0])
})
document.getElementById('btn-test').onclick = runSelfTest
```

### Help Popover

**Lines:** 836-844

**Triggers:**
- Click `#help-btn`: Toggle popover
- Click outside: Close popover
- Press Escape: Close popover

**Accessibility:**
- Sets `aria-expanded` attribute
- Keyboard support (Escape key)

---

## Constants

### `ROLE_ORDER`

**Lines:** 179

```javascript
const ROLE_ORDER = ["None", "Read", "Contribute", "Edit", "Full Control"]
```

**Purpose:** Define role precedence for `highestRole()` comparison

---

### `LINK_TYPES`

**Lines:** 180-185

```javascript
const LINK_TYPES = [
  {id: 'anyone', label: 'Anyone with the link', desc: 'Anonymous access, no sign-in required'},
  {id: 'org', label: 'People in your organization', desc: 'All company members (authenticated)'},
  {id: 'specific', label: 'Specific people', desc: 'Only specified users (like direct assignment)'},
  {id: 'existing', label: 'People with existing access', desc: 'No new permissions, just a URL'}
]
```

**Purpose:** Define sharing link types with labels and descriptions

---

## Global Objects

### `app`

**Lines:** 191-198

```javascript
const app = {
  state: {
    principals: [],
    nodes: [],
    selectedId: null,
    schemaVersion: 2
  }
}
```

**Purpose:** Central state container

**Access:** All functions reference `app.state`

---

## Initialization

**Lines:** 880-881

```javascript
sample()
render()
```

**Purpose:** Load sample scenario on page load

**Behavior:** Immediately makes tool interactive with demo data

---

## Usage Examples

### Example 1: Programmatically Add User and Grant Permission

```javascript
// Add user to directory
const userId = makeId()
app.state.principals.push({
  id: userId,
  name: "John Doe",
  kind: "user"
})

// Grant Edit permission on selected node
const selected = byId(app.state.selectedId)
selected.assignments.push({
  principalId: userId,
  role: "Edit"
})

// Re-render UI
render()
```

### Example 2: Create Folder with Unique Permissions

```javascript
// Get parent library
const library = app.state.nodes.find(n => n.type === 'library')

// Create folder
const folderId = makeId()
const folder = {
  id: folderId,
  type: 'folder',
  name: 'Confidential',
  parentId: library.id,
  inherits: false,  // Break inheritance
  assignments: [],
  sharingLinks: []
}
app.state.nodes.push(folder)

// Grant to specific user
folder.assignments.push({
  principalId: userId,
  role: 'Full Control'
})

render()
```

### Example 3: Check if User Has Access

```javascript
const file = byId('file123')
const eff = computeEffectiveDetailed(file)

if (eff[userId]) {
  console.log(`User has ${eff[userId].role} access`)
  console.log('Sources:', eff[userId].sources)
} else {
  console.log('User has no access')
}
```

### Example 4: Find All High-Risk Items

```javascript
const highRiskItems = app.state.nodes.filter(node => {
  const risk = riskOf(node)
  return risk.level === 'High'
})

console.log('High-risk items:', highRiskItems.map(n => n.name))
```

### Example 5: Export Custom Report

```javascript
const report = app.state.nodes.map(node => {
  const eff = computeEffectiveDetailed(node)
  const accessCount = Object.keys(eff).length
  const risk = riskOf(node)

  return {
    path: pathOf(node),
    type: node.type,
    inherits: node.inherits,
    accessCount: accessCount,
    risk: risk.level
  }
})

console.table(report)
```

---

## Type Safety Notes

This is vanilla JavaScript with no TypeScript compilation. The TypeScript interfaces in this document are for reference only.

To add actual type safety:
1. Rename file to `.ts`
2. Add TypeScript interfaces
3. Set up build process
4. Compile to JavaScript

For inline type hints without compilation, consider using JSDoc:

```javascript
/**
 * @typedef {Object} Node
 * @property {string} id
 * @property {'site'|'library'|'folder'|'file'} type
 * @property {string} name
 * @property {string|null} parentId
 * @property {boolean} inherits
 * @property {Assignment[]} assignments
 * @property {SharingLink[]} sharingLinks
 */

/**
 * Find node by ID
 * @param {string} id - Node identifier
 * @returns {Node|undefined}
 */
function byId(id) {
  return app.state.nodes.find(n => n.id === id)
}
```
