Skip to content

🎨 ZSHAND Style Guide¢

πŸ“‹ This guide covers naming conventions, error handling patterns, zsh idioms, and code style rules used throughout the framework.

🧠 Philosophy

Consistency over cleverness. Every file should look like it was written by the same person. When in doubt, match the existing code around you.


πŸ”’ File NamingΒΆ

πŸ“‚ Numbered PrefixesΒΆ

Files in ordered directories use 2-digit numeric prefixes to enforce deterministic load order:

Directory Convention Example
core/ NN_name.zsh (even: framework) 02_vars.zsh, 06_engine.zsh
shared_functions/ NN_name.zsh 01_stderr_error.zsh
startup/ NN_az_name.zsh 24_az_safe_mode.zsh
hooks/ NN_name.zsh 01_mise.zsh, 20_docker.zsh

Rules:

  • πŸ”΅ Even numbers (02, 04, 06...) β†’ framework files
  • 🟒 Odd numbers (01, 03, 05...) β†’ reserved for user insertion
  • πŸ“ 2-number gaps between framework files allow interleaving

πŸ“ Unnumbered DirectoriesΒΆ

Directory Convention Example
functions/ name.zsh (descriptive) dcopy.zsh, zopt.zsh
widgets/ name.zsh (descriptive) aicmit.zsh, bufcopy.zsh
bin/ name (no extension, executable) aicontext, zr
private_functions/ _name.zsh (underscore prefix) _zshand_network_monitor.zsh

🏷️ Naming Conventions¢

πŸ”€ FunctionsΒΆ

Scope Convention Example Where
🌐 Public Short, memorable, lowercase zcheck, zprime, dcopy functions/, core/
πŸ”’ Framework-internal _az_ prefix _az_safe_mode_enter, _az_zwc_stale_check startup/, core/
πŸ”’ Module-private _zshand_ prefix _zshand_profile_bundle_report build/, private_functions/
🧱 Shared utility Descriptive, snake_case stderr_error, load_file_timed shared_functions/
⌨️ Widget Short, descriptive aicmit, bufcopy, aidebug widgets/
πŸ”§ Helper (file-local) _name (single underscore) _zlog_get_path, _fadd Same file only

πŸ”€ VariablesΒΆ

Scope Convention Example
🌍 Exported env UPPER_SNAKE_CASE ZSHAND_DEV_MODE, LOG_DIR
🌍 Framework env ZSHAND_ prefix ZSHAND_CACHE_DIR, ZSHAND_PERF_BUDGET
πŸ”’ Internal global _zshand_ prefix _zshand_timings, _zshand_slow_files
πŸ“¦ Local lower_snake_case local cache_dir, local timing_log
πŸ”„ Loop Short, contextual local f, local hook, local bname
πŸ“Š Associative array Descriptive, _ prefix for internal typeset -gA _zshand_timings

πŸ”€ Key Variable PrefixesΒΆ

Prefix Meaning Example
ZSHAND_ Framework configuration (exported) ZSHAND_DEBUG
_ZSHAND_ Internal framework state (not exported) _ZSHAND_CACHE_DIR
_az_ Startup/internal function prefix _az_safe_mode_enter
_zshand_ Module-level internal state _zshand_full_bundle_loaded

πŸ“ Code PatternsΒΆ

βœ… Error HandlingΒΆ

Use the shared functions for consistent error output:

# βœ… Good: Use shared functions
stderr_error "Configuration file not found: $config_file"
stderr_warn "Falling back to default settings"

# ❌ Bad: Raw echo to stderr
echo "ERROR: file not found" >&2

βœ… Guard AssertionsΒΆ

Use assertion functions before risky operations:

# βœ… Good: Assert before use
if assert_dir_exists "$ZSHAND/core" "Core directory"; then
    source "$ZSHAND/core/02_vars.zsh"
fi

if assert_function_exists "zprime"; then
    zprime
fi

# ❌ Bad: Silent failure
[[ -d "$ZSHAND/core" ]] && source "$ZSHAND/core/02_vars.zsh"

βœ… Graceful DegradationΒΆ

Functions should never crash the shell. Handle missing deps gracefully:

# βœ… Good: Check and degrade
my_function() {
    (( $+commands[jq] )) || {
        stderr_warn "jq not installed. Install: pacman -S jq"
        return 1
    }
    # ... proceed with jq
}

# ❌ Bad: Assume deps exist
my_function() {
    jq '.key' "$file"  # Crashes if jq missing
}

βœ… Local VariablesΒΆ

Always declare variables local in functions:

# βœ… Good: Explicit local
my_function() {
    local cache_dir="${ZSHAND_CACHE_DIR}"
    local -a files=("$cache_dir"/*.zsh(N))
    local -A timings=()
}

# ❌ Bad: Implicit globals (leak into caller)
my_function() {
    cache_dir="${ZSHAND_CACHE_DIR}"
    files=("$cache_dir"/*.zsh(N))
}

βœ… Global VariablesΒΆ

When a variable must be global, use typeset -g explicitly:

# βœ… Good: Explicit global declaration
typeset -g _zshand_full_bundle_loaded=0
typeset -gA _zshand_timings

# ❌ Bad: Implicit global (confusing in function context)
_zshand_full_bundle_loaded=0

βœ… Parameter ExpansionΒΆ

Prefer zsh parameter expansion over external commands:

# βœ… Good: Builtins
local basename="${path:t}"       # basename
local dirname="${path:h}"        # dirname
local extension="${file:e}"      # extension
local no_ext="${file:r}"         # remove extension
local absolute="${file:A}"       # absolute path

# ❌ Bad: Subprocesses
local basename="$(basename "$path")"
local dirname="$(dirname "$path")"

βœ… Conditional PatternsΒΆ

# βœ… Good: Double-bracket test (zsh native)
[[ -f "$file" ]] && source "$file"
[[ -z "$var" ]] && var="default"

# βœ… Good: Arithmetic evaluation
(( ZSHAND_DEBUG )) && print "debug info"
(( $+commands[jq] )) || return 1
(( $+functions[zprime] )) && zprime

# ❌ Bad: Single-bracket test (POSIX, slower)
[ -f "$file" ] && source "$file"

βœ… Array PatternsΒΆ

# βœ… Good: Glob with nullglob qualifier
local -a files=("$dir"/*.zsh(N.))    # (N) = no error if empty, (.) = files only

# βœ… Good: Sorted glob with numeric sort
for f in "$dir"/*.zsh(nN.); do       # (n) = numeric sort
    source "$f"
done

# ❌ Bad: Bare glob (errors on empty match)
for f in "$dir"/*.zsh; do
    source "$f"
done

βœ… Default ValuesΒΆ

# βœ… Good: Parameter expansion with default
local cache="${ZSHAND_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/zshand}"

# βœ… Good: Colon-equals for set-if-unset
: ${ZSHAND_PERF_BUDGET:=100}

# ❌ Bad: Explicit if/else for defaults
if [[ -z "$ZSHAND_CACHE_DIR" ]]; then
    local cache="$HOME/.cache/zshand"
else
    local cache="$ZSHAND_CACHE_DIR"
fi

πŸ“ Formatting RulesΒΆ

πŸ“ IndentationΒΆ

  • 2 spaces for indentation (no tabs)
  • Enforced by .editorconfig and shfmt

πŸ“ Line LengthΒΆ

  • 80 characters soft limit for code
  • 78 characters for header divider content (after #)
  • Long strings: break with \ continuation or use heredocs

πŸ“ Blank LinesΒΆ

  • 1 blank line between functions
  • 1 blank line before section comments
  • No trailing blank lines at end of file
  • 1 trailing newline at end of file (enforced by .editorconfig)

πŸ“ QuotingΒΆ

# βœ… Always quote variable expansions in arguments
source "$ZSHAND/core/02_vars.zsh"
[[ -f "$config_file" ]]
print -P "%F{green}$message%f"

# βœ… Exception: Arithmetic context (no quotes needed)
(( ZSHAND_DEBUG ))
(( $+commands[jq] ))

# ❌ Bad: Unquoted variables (word splitting risk)
source $ZSHAND/core/02_vars.zsh
[[ -f $config_file ]]

πŸ’¬ CommentsΒΆ

πŸ“ Section CommentsΒΆ

Use the standard divider format for major sections within a file:

# ── SECTION NAME ──────────────────────────────────────────────────────────────
# Description of what this section does.

πŸ“ Inline CommentsΒΆ

# βœ… Good: Explain WHY, not WHAT
local -gU path  # Enforce unique entries to keep lookups fast

# βœ… Good: Explain zsh-specific idioms
[[ ${arr[(ie)$item]} -le ${#arr} ]]  # (ie) = exact case-insensitive search

# ❌ Bad: Restate the obvious
local count=0  # Set count to 0

πŸ“ TODO/FIXMEΒΆ

# TODO: Add spec for ZSHAND_PROFILE_VERBOSE=1 output differences
# FIXME: This breaks when path contains newlines
# HACK: Workaround for zsh 5.0 compatibility (remove after 5.2+ minimum)

πŸ”’ Security PatternsΒΆ

πŸ”‘ SecretsΒΆ

# βœ… Good: Secure file permissions
chmod 600 "$ZSHAND_CONFIG_DIR/secrets.zsh"

# βœ… Good: Never log secrets
_zrun --quiet "action" "description"  # Telemetry without secrets

# ❌ Bad: Echo secrets to log
echo "API_KEY=$API_KEY" >> "$LOG_DIR/debug.log"

πŸ›‘οΈ Input ValidationΒΆ

# βœ… Good: Validate before use
[[ -z "$1" ]] && { stderr_error "Missing argument"; return 1 }
[[ -d "$1" ]] || { stderr_error "Not a directory: $1"; return 1 }

# βœ… Good: Quote everything in eval-adjacent contexts
local cmd="${1:?Command required}"

🫧 Unified IO (Preferred)¢

All IO output should use the shared IO library (00_gum_io.zsh). This provides automatic gum enhancement when available, with graceful fallback.

# βœ… Preferred: Use IO library functions
stderr_error "Something failed"       # Red error to stderr
stderr_warn "Something suspicious"    # Yellow warning to stderr
echo_info "Processing..."            # Blue info to stdout
echo_ok "Done"                       # Green success to stdout

# βœ… Rich output functions
az_banner "Module Title"              # Double-border title banner
az_header "Section"                   # Bold section header
az_divider                            # Horizontal line
az_step 1 5 "Installing packages"    # Step counter [1/5]
az_done "Complete!"                   # Green completion banner
az_note "Important: Install fonts"    # Callout box

# βœ… Interactive functions
az_spin "Compiling..." zcompile f.zsh # Spinner with βœ“/βœ—
az_confirm "Continue?" && echo "yes"  # Y/N prompt
selected=$(az_choose "A" "B" "C")    # Selection menu

# ❌ Avoid: Raw print -P for simple error/warn/info/ok messages
print -P "%F{red}ERROR: $message%f" >&2    # Use stderr_error instead
print -P "%F{yellow}Warning: $msg%f" >&2   # Use stderr_warn instead

🎨 Inline print -P (When Appropriate)¢

Inline print -P is still appropriate for complex multi-color display that cannot be expressed as a single function call:

# βœ… Acceptable: Complex dashboard display with multiple colors and variables
print -P "  %F{cyan}System:%f  Arch (%F{yellow}${session_type}%f) | Up: ${uptime}"
print -P "  %F{green}%3d. [βœ“]%f ${path_entry}"

# βœ… Acceptable: Interactive prompts with colored inline text
print -Pn "%F{cyan}σ°ƒ’ Continue? (y/n):%f "

# βœ… Acceptable: Template code written to files
cat <<EOF > "$file"
  print -P "󰄬 Executing ${name}..."
EOF

πŸ“Ÿ Nerd Font IconsΒΆ

The framework uses Nerd Font icons consistently:

Icon Meaning Usage
󰄬 Success / check zcheck pass items
󱈸 Failure / error zcheck fail items
σ°ƒ’ Warning / stale zcheck warnings
󰚚 System / audit Report headers
σ°‘“ Sync / loading Progress indicators
󰉋 Directory Directory operations
󰒍 Bootstrap Setup operations

Document Purpose
πŸ“ HEADER_STANDARD.md Documentation header conventions
🀝 CONTRIBUTING.md Workflow and quality gate
πŸ—οΈ ARCHITECTURE.md Directory structure and naming
⚑ PERFORMANCE.md Builtin-over-subprocess patterns
πŸ“š Documentation Index Full doc nav