Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/guest-portfolio.dev/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Terminal.js is the central orchestrator of the portfolio. It renders the full command history, wires up keyboard input through TerminalInput, parses every typed command via a switch dispatch, and outputs richly styled React nodes for each result. It also mounts a desktop-only CommandPalette sidebar so visitors can click commands rather than type them.

TerminalContext

All shared runtime state lives in TerminalContext. The useTerminal() hook provides the following shape to any consumer:
const {
  history,          // HistoryEntry[]  — ordered list of past commands & outputs
  cwd,              // string          — current working directory (e.g. "~")
  theme,            // "green" | "amber"
  isBooting,        // boolean         — true while BootSequence is visible
  addHistory,       // (command: string, output: ReactNode) => void
  clearHistory,     // () => void
  setCwd,           // (path: string) => void
  setTheme,         // (theme: "green" | "amber") => void
  setIsBooting,     // (v: boolean) => void
  executeCommand,   // (cmd: string) => void  — reference to processCommand
  setExecuteCommand,// (fn) => void           — registers processCommand
  reduceMotion,     // boolean
  setReduceMotion,  // (v: boolean) => void
} = useTerminal();
Each history entry stored by addHistory is shaped as:
{
  id: string,        // random base-36 id
  command: string,   // raw input string (empty string for auto-outputs)
  output: ReactNode, // rendered React node
  cwd: string,       // snapshot of cwd at time of execution
}
setExecuteCommand is called inside Terminal with () => processCommand so that the CommandPalette sidebar can invoke executeCommand(cmd) to programmatically trigger any command — the same code path as typing it at the prompt. Without this indirection, CommandPalette would have no access to processCommand, since it lives in a sibling subtree.

Layout Structure

<div className="flex-1 overflow-y-auto pr-4 pb-16 font-mono text-sm md:text-base">
  {showMatrix && <MatrixRain />}           {/* full-screen canvas overlay */}

  {history.map((entry) => (
    <div key={entry.id} className="mb-2">
      {entry.command !== "" && (
        <div className="flex items-center">
          <span className="text-terminal-green mr-2 whitespace-nowrap">
            guest@portfolio.dev:
            <span className="text-terminal-amber">{entry.cwd}</span>$
          </span>
          <span className="text-terminal-white">{entry.command}</span>
        </div>
      )}
      {entry.output && <div className="mt-1">{entry.output}</div>}
    </div>
  ))}

  <TerminalInput onCommand={processCommand} />
  <div ref={bottomRef} />                  {/* scroll anchor */}
</div>
The CommandPalette is rendered as a sibling in the parent layout:
<div className="hidden lg:block w-64 border-l border-terminal-dim pl-4 ml-4 h-full overflow-y-auto">
  {/* QUICK COMMANDS + SYSTEM sections */}
</div>

Prompt Format

Every history entry renders the prompt in the pattern:
guest@portfolio.dev:<cwd>$ <command>
  • guest@portfolio.dev: — rendered in green (text-terminal-green)
  • <cwd> — rendered in amber (text-terminal-amber), e.g. ~ or ~/projects
  • $ and the command text — rendered in white (text-terminal-white)
The live TerminalInput at the bottom renders an identical prompt prefix so the active line matches the history lines visually.

Auto-scroll

A useRef anchor <div> is placed after the last history entry and after TerminalInput. Whenever history changes, a useEffect fires scrollIntoView({ behavior: 'smooth' }) to keep the newest output visible:
const bottomRef = useRef(null);

useEffect(() => {
  bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [history]);

Command Dispatch

Commands are parsed by splitting the raw input string on whitespace, lower-casing the first token, then switching on it. The full set of recognised commands:
CommandOutput component / behaviour
helpInline two-column command reference grid
clearCalls clearHistory(), returns immediately
whoami<AboutOutput />
cat about.md<AboutOutput />
./skills.sh<SkillsOutput />
git log<GitLogOutput />
git <other>Inline error: git: '<token>' is not a git command
mail [subject]<MailOutput subject={...} />
man <page><ManOutput page={...} />
man (no arg)Inline: What manual page do you want?
ls [-la] [dir]Directory listing from virtual filesystem
cd [dir]Updates cwd via setCwd
cat <file>File content from virtual filesystem
theme green|amberCalls setTheme, confirms inline
sudo make me a sandwichEaster egg 🥪
sudo <other>”not in the sudoers file” error
matrixShows <MatrixRain /> for 5 seconds
cowsay [text]ASCII cow <pre> block
vim / emacs / nanoRedirects to mail
<unknown>Red div: command not found: <token>
Unrecognised commands render:
<div className="text-terminal-red">
  command not found: {token}
</div>

Filesystem Commands

ls and cd resolve paths using the resolveNode(cwd, target) utility from utils/fileSystem.js. Errors (e.g. path not found, not a directory) render in text-terminal-red. Directory listings colour entries based on node.type: "dir" → amber bold, "exec" → green bold, regular files → unstyled white.

Initial Render

When the history array is empty (first load or after clear), a useEffect fires once and calls addHistory("", <AsciiBanner />) to render the welcome banner with no prompt prefix.
useEffect(() => {
  if (history.length === 0) {
    addHistory("", <AsciiBanner />);
  }
}, []);

CommandPalette Integration

CommandPalette reads executeCommand from context and calls it when a quick-command button is clicked. It is hidden on small/medium screens (hidden lg:block) and sits in a fixed-width w-64 sidebar with two sections:
  • QUICK COMMANDS — 8 pre-wired commands: help, whoami, ls projects/, ./skills.sh, git log --career, ls -la blog/, mail, clear
  • SYSTEMtheme amber / theme green toggle buttons

TerminalInput Behaviour

TerminalInput renders the active prompt line. Key behaviours:
  • Enter — submits the current value, calls onCommand(value), resets input
  • ↑ / ↓ — navigates command history (filtered to non-empty commands)
  • Tab — tab-completion for command names and filesystem paths
  • Click anywhere — auto-focuses the input so typing always works
  • The visible cursor is a <span className="typewriter-cursor bg-terminal-primary"> block overlay; the real <input> uses caret-transparent

Build docs developers (and LLMs) love