#!/usr/bin/env perl

use strict;
use warnings;
use utf8;

use Config ();
use Capture::Tiny qw(capture);
use Cwd qw(cwd);
use FindBin qw($Bin);
use lib "$Bin/../lib";
use File::Spec;
use Getopt::Long qw(GetOptionsFromArray);
use Getopt::Long ();
use IO::Handle ();
use IO::Select ();
use IPC::Open3 qw(open3);
use JSON::XS ();
use Pod::Usage qw(pod2usage);
use Symbol qw(gensym);

binmode STDOUT, ':encoding(UTF-8)';
binmode STDERR, ':encoding(UTF-8)';

use Developer::Dashboard::Platform qw(
  command_argv_for_path
  is_runnable_file
  native_shell_name
  normalize_shell_name
  resolve_runnable_file
  shell_quote_for
);
use Developer::Dashboard::InternalCLI ();

my $dashboard_lib = File::Spec->catdir( $Bin, '..', 'lib' );
if ( -d $dashboard_lib ) {
    my $path_sep = $Config::Config{path_sep} || ':';
    my @existing = grep { defined && $_ ne '' } split /\Q$path_sep\E/, ( $ENV{PERL5LIB} || '' );
    if ( !grep { $_ eq $dashboard_lib } @existing ) {
        $ENV{PERL5LIB} = join $path_sep, $dashboard_lib, @existing;
    }
}

my $cmd = shift @ARGV || '';
_prime_command_result_env( $cmd, @ARGV ) if $cmd ne '';

if ( $cmd eq '' ) {
    pod2usage(
        -exitval => 1,
        -verbose => 99,
        -sections => [ qw(NAME SYNOPSIS) ],
    );
}
elsif ( $cmd eq 'help' || $cmd eq '--help' || $cmd eq '-h' ) {
    pod2usage(
        -exitval => 0,
        -verbose => 99,
    );
}

# Handle query commands directly (don't try to exec them as files)
if ( $cmd && $cmd =~ /^(pjq|jq|pyq|yq|ptomq|tomq|pjp|propq|iniq|csvq|xmlq)$/ ) {
    require Developer::Dashboard::PathRegistry;
    my $paths = Developer::Dashboard::PathRegistry->new(
        workspace_roots => [ grep { defined && -d } map { "$ENV{HOME}/$_" } qw(projects src work) ],
        project_roots   => [ grep { defined && -d } map { "$ENV{HOME}/$_" } qw(projects src work) ],
    );
    Developer::Dashboard::InternalCLI::ensure_helpers( paths => $paths );
    require Developer::Dashboard::CLI::Query;
    Developer::Dashboard::CLI::Query::run_query_command( command => $cmd, args => \@ARGV );
    exit 0;
}

if ( $cmd eq 'of' || $cmd eq 'open-file' ) {
    require Developer::Dashboard::PathRegistry;
    require Developer::Dashboard::CLI::OpenFile;
    my $paths = Developer::Dashboard::PathRegistry->new(
        workspace_roots => [ grep { defined && -d } map { "$ENV{HOME}/$_" } qw(projects src work) ],
        project_roots   => [ grep { defined && -d } map { "$ENV{HOME}/$_" } qw(projects src work) ],
    );
    Developer::Dashboard::CLI::OpenFile::run_open_file_command( paths => $paths, args => \@ARGV );
    exit 0;
}

require Developer::Dashboard::Auth;
require Developer::Dashboard::ActionRunner;
require Developer::Dashboard::Codec;
Developer::Dashboard::Codec->import(qw(encode_payload decode_payload));
require Developer::Dashboard::CollectorRunner;
require Developer::Dashboard::Collector;
require Developer::Dashboard::Config;
require Developer::Dashboard;
require Developer::Dashboard::DockerCompose;
require Developer::Dashboard::Doctor;
require Developer::Dashboard::FileRegistry;
require Developer::Dashboard::IndicatorStore;
require Developer::Dashboard::JSON;
Developer::Dashboard::JSON->import(qw(json_encode));
require Developer::Dashboard::PageDocument;
require Developer::Dashboard::PageRuntime;
require Developer::Dashboard::PageResolver;
require Developer::Dashboard::PageStore;
require Developer::Dashboard::PathRegistry;
require Developer::Dashboard::Prompt;
require Developer::Dashboard::RuntimeManager;
require Developer::Dashboard::SessionStore;
require Developer::Dashboard::Web::App;
require Developer::Dashboard::Web::Server;

my $paths = Developer::Dashboard::PathRegistry->new(
    workspace_roots => [ grep { defined && -d } map { "$ENV{HOME}/$_" } qw(projects src work) ],
    project_roots   => [ grep { defined && -d } map { "$ENV{HOME}/$_" } qw(projects src work) ],
);
_apply_runtime_perl5lib( paths => $paths );
my $files      = Developer::Dashboard::FileRegistry->new( paths => $paths );
my $indicators = Developer::Dashboard::IndicatorStore->new( paths => $paths );
my $collectors = Developer::Dashboard::Collector->new( paths => $paths );
my $runner     = Developer::Dashboard::CollectorRunner->new(
    collectors => $collectors,
    files      => $files,
    indicators => $indicators,
    paths      => $paths,
);
my $config     = Developer::Dashboard::Config->new( files => $files, paths => $paths );
my $pages      = Developer::Dashboard::PageStore->new( paths => $paths );
my $page_runtime = Developer::Dashboard::PageRuntime->new(
    files   => $files,
    paths   => $paths,
    aliases => $config->path_aliases,
);
my $auth = Developer::Dashboard::Auth->new(
    files => $files,
    paths => $paths,
);
my $actions = Developer::Dashboard::ActionRunner->new(
    files => $files,
    paths => $paths,
);
my $resolver = Developer::Dashboard::PageResolver->new(
    actions => $actions,
    config  => $config,
    pages   => $pages,
    paths   => $paths,
);
my $docker = Developer::Dashboard::DockerCompose->new(
    config  => $config,
    paths   => $paths,
);
my $doctor = Developer::Dashboard::Doctor->new(
    paths => $paths,
);
my $prompt = Developer::Dashboard::Prompt->new(
    paths      => $paths,
    indicators => $indicators,
);
my $sessions = Developer::Dashboard::SessionStore->new( paths => $paths );
my $runtime = Developer::Dashboard::RuntimeManager->new(
    app_builder => sub {
        my (%args) = @_;
        my $web_auth = Developer::Dashboard::Auth->new( files => $files, paths => $paths );
        my $web_pages = Developer::Dashboard::PageStore->new( paths => $paths );
        my $web_sessions = Developer::Dashboard::SessionStore->new( paths => $paths );
        my $app = Developer::Dashboard::Web::App->new(
            actions  => $actions,
            auth     => $web_auth,
            config   => $config,
            pages    => $web_pages,
            prompt   => $prompt,
            runtime  => $page_runtime,
            resolver => $resolver,
            sessions => $web_sessions,
        );
        return Developer::Dashboard::Web::Server->new(
            app     => $app,
            host    => $args{host},
            port    => $args{port},
            workers => $args{workers},
            ssl     => $args{ssl},
        );
    },
    config => $config,
    files  => $files,
    paths  => $paths,
    runner => $runner,
);

my $CONFIG_PATH_ALIASES_LOADED  = 0;
my $SAVED_PAGES_MIGRATED        = 0;
my $COLLECTOR_INDICATORS_SYNCED = 0;

# _prime_ticket_ref_env()
# Seeds TICKET_REF from the active tmux session when the shell environment does
# not already provide it.
# Input: none.
# Output: true after best-effort environment priming.
sub _prime_ticket_ref_env {
    return 1 if defined $ENV{TICKET_REF} && $ENV{TICKET_REF} ne '';
    my $ticket = _tmux_ticket_ref();
    $ENV{TICKET_REF} = $ticket if defined $ticket && $ticket ne '';
    return 1;
}

# _tmux_ticket_ref()
# Reads TICKET_REF from tmux session environment when available.
# Input: none.
# Output: ticket string or undef when tmux does not expose one.
sub _tmux_ticket_ref {
    my ( $stdout, undef, $exit_code ) = capture {
        system 'tmux', 'show-environment', 'TICKET_REF';
        return $? >> 8;
    };
    return if $exit_code != 0;
    return if !defined $stdout || $stdout eq '';
    for my $line ( split /\n/, $stdout ) {
        next if !defined $line || $line eq '' || $line =~ /^-/;
        return $1 if $line =~ /^TICKET_REF=(.+)$/;
    }
    return;
}

sub _load_configured_path_aliases {
    return 1 if $CONFIG_PATH_ALIASES_LOADED;
    $paths->register_named_paths( $config->path_aliases );
    $CONFIG_PATH_ALIASES_LOADED = 1;
    return 1;
}

sub _migrate_saved_pages {
    return 1 if $SAVED_PAGES_MIGRATED;
    $pages->migrate_legacy_json_pages;
    $SAVED_PAGES_MIGRATED = 1;
    return 1;
}

sub _collector_jobs {
    return $config->collectors;
}

sub _sync_configured_collector_indicators {
    return 1 if $COLLECTOR_INDICATORS_SYNCED;
    $indicators->sync_collectors( _collector_jobs() );
    $COLLECTOR_INDICATORS_SYNCED = 1;
    return 1;
}

# _runtime_cpanfile_path(%args)
# Resolves the project-local cpanfile used by dashboard cpan installs.
# Input: hash containing an optional path registry under the "paths" key.
# Output: absolute cpanfile path string.
sub _runtime_cpanfile_path {
    my (%args) = @_;
    my $active_paths = $args{paths} || $paths;
    return File::Spec->catfile( $active_paths->runtime_root, 'cpanfile' );
}

# _runtime_local_root(%args)
# Resolves and creates the project-local cpanm installation root.
# Input: hash containing an optional path registry under the "paths" key.
# Output: absolute local-root path string.
sub _runtime_local_root {
    my (%args) = @_;
    my $active_paths = $args{paths} || $paths;
    my $dir = File::Spec->catdir( $active_paths->runtime_root, 'local' );
    if ( !-d $dir ) {
        require File::Path;
        File::Path::make_path($dir);
    }
    if ( $active_paths->can('secure_dir_permissions') ) {
        $active_paths->secure_dir_permissions($dir);
    }
    return $dir;
}

# _runtime_local_lib(%args)
# Resolves the runtime-local Perl library directory exposed to dashboard subprocesses.
# Input: hash containing an optional path registry under the "paths" key.
# Output: absolute local Perl library path string.
sub _runtime_local_lib {
    my (%args) = @_;
    return File::Spec->catdir( _runtime_local_root(%args), 'lib', 'perl5' );
}

# _runtime_perl5lib_env(%args)
# Builds the PERL5LIB environment update for runtime-local optional modules.
# Input: hash containing an optional path registry under the "paths" key.
# Output: hash containing the merged PERL5LIB value.
sub _runtime_perl5lib_env {
    my (%args) = @_;
    my $path_sep  = $Config::Config{path_sep} || ':';
    my $local_lib = _runtime_local_lib(%args);
    my @perl5lib  = grep { defined $_ && $_ ne '' } split /\Q$path_sep\E/, ( $ENV{PERL5LIB} || '' );
    unshift @perl5lib, $local_lib if -d $local_lib && !grep { $_ eq $local_lib } @perl5lib;
    return (
        PERL5LIB => join( $path_sep, @perl5lib ),
    );
}

# _apply_runtime_perl5lib(%args)
# Exposes the runtime-local Perl library to the current dashboard process.
# Input: hash containing an optional path registry under the "paths" key.
# Output: true value.
sub _apply_runtime_perl5lib {
    my (%args) = @_;
    my %env = _runtime_perl5lib_env(%args);
    @ENV{ keys %env } = values %env;
    return 1;
}

# _normalize_runtime_cpan_modules(@modules)
# Validates requested module names, trims whitespace, and removes duplicates while preserving order.
# Input: zero or more requested module-name strings.
# Output: ordered list of validated module-name strings.
sub _normalize_runtime_cpan_modules {
    my (@modules) = @_;
    my @normalized;
    my %seen;
    for my $module (@modules) {
        next if !defined $module;
        $module =~ s/^\s+//;
        $module =~ s/\s+$//;
        next if $module eq '';
        die "Invalid module name '$module'\n" if $module !~ /\A[A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z0-9_]+)*\z/;
        next if $seen{$module}++;
        push @normalized, $module;
    }
    return @normalized;
}

# _effective_runtime_cpan_modules(@modules)
# Expands requested module names so any requested DBD driver also installs DBI.
# Input: ordered list of validated requested module-name strings.
# Output: ordered list of module names that should be installed and persisted.
sub _effective_runtime_cpan_modules {
    my (@modules) = @_;
    my @effective = @modules;
    if ( grep { /^DBD::/ } @modules ) {
        unshift @effective, 'DBI' if !grep { $_ eq 'DBI' } @effective;
    }
    my %seen;
    return grep { !$seen{$_}++ } @effective;
}

# _append_runtime_cpan_modules(%args)
# Appends missing module requirements to the runtime cpanfile without duplicating existing entries.
# Input: hash containing an optional path registry under the "paths" key plus a required array reference under "modules".
# Output: absolute cpanfile path string.
sub _append_runtime_cpan_modules {
    my (%args) = @_;
    my $active_paths = $args{paths} || $paths;
    my $modules      = $args{modules};
    die "No modules provided\n" if ref($modules) ne 'ARRAY' || !@{$modules};
    my $cpanfile = _runtime_cpanfile_path( paths => $active_paths );
    my %existing;
    if ( -f $cpanfile ) {
        open my $in, '<', $cpanfile or die "Unable to read $cpanfile: $!";
        while ( my $line = <$in> ) {
            next if $line !~ /requires\s+'([^']+)'/;
            $existing{$1} = 1;
        }
        close $in or die "Unable to close $cpanfile: $!";
    }
    open my $out, '>>', $cpanfile or die "Unable to append $cpanfile: $!";
    for my $module ( @{$modules} ) {
        next if $existing{$module};
        print {$out} "requires '$module';\n";
    }
    close $out or die "Unable to close $cpanfile: $!";
    if ( $active_paths->can('secure_file_permissions') ) {
        $active_paths->secure_file_permissions($cpanfile);
    }
    return $cpanfile;
}

# _install_runtime_cpan_modules(%args)
# Installs optional runtime Perl modules into the project-local local-lib tree and records them in the runtime cpanfile.
# Input: hash containing an optional path registry under the "paths" key and a required array reference of requested modules under "modules".
# Output: result hash reference containing ok/error state, effective module list, paths, and captured command output.
sub _install_runtime_cpan_modules {
    my (%args) = @_;
    my $active_paths = $args{paths} || $paths;
    my $modules      = $args{modules};
    return { error => 'No modules provided' } if ref($modules) ne 'ARRAY' || !@{$modules};

    my @requested = _normalize_runtime_cpan_modules( @{$modules} );
    return { error => 'No valid modules provided' } if !@requested;

    my @install_modules = _effective_runtime_cpan_modules(@requested);
    my $local_root      = _runtime_local_root( paths => $active_paths );

    my ( $stdout, $stderr, $exit ) = capture {
        system( 'cpanm', '-L', $local_root, @install_modules );
        return $? >> 8;
    };
    return {
        error        => "Failed to install runtime Perl modules with cpanm: $stderr",
        stdout       => $stdout,
        stderr       => $stderr,
        requested    => \@requested,
        installed    => \@install_modules,
        runtime_root => $active_paths->runtime_root,
        local_root   => $local_root,
        cpanfile     => _runtime_cpanfile_path( paths => $active_paths ),
    } if $exit != 0;

    _append_runtime_cpan_modules(
        paths   => $active_paths,
        modules => \@install_modules,
    );
    _apply_runtime_perl5lib( paths => $active_paths );

    return {
        ok           => 1,
        stdout       => $stdout,
        stderr       => $stderr,
        requested    => \@requested,
        installed    => \@install_modules,
        runtime_root => $active_paths->runtime_root,
        local_root   => $local_root,
        cpanfile     => _runtime_cpanfile_path( paths => $active_paths ),
    };
}

if ( $cmd eq 'ps1' ) {
    _sync_configured_collector_indicators();
    _prime_ticket_ref_env();
    my $jobs = 0;
    my $cwd  = cwd();
    my $mode = 'compact';
    my $color = 0;
    my $max_age = 300;
    GetOptionsFromArray(
        \@ARGV,
        'jobs=i' => \$jobs,
        'cwd=s'  => \$cwd,
        'mode=s' => \$mode,
        'color!' => \$color,
        'max-age=i' => \$max_age,
    );
    print $prompt->render( jobs => $jobs, cwd => $cwd, mode => $mode, color => $color, max_age => $max_age );
    exit 0;
}
elsif ( $cmd eq 'paths' ) {
    _load_configured_path_aliases();
    my %out = (
        home           => $paths->home,
        home_runtime_root => $paths->home_runtime_root,
        project_runtime_root => scalar $paths->project_runtime_root,
        runtime_root   => $paths->runtime_root,
        state_root     => $paths->state_root,
        cache_root     => $paths->cache_root,
        logs_root      => $paths->logs_root,
        dashboards_root=> $paths->dashboards_root,
        bookmarks_root => $paths->bookmarks_root,
        cli_root       => $paths->cli_root,
        collectors_root=> $paths->collectors_root,
        indicators_root=> $paths->indicators_root,
        config_root    => $paths->config_root,
        current_project_root => scalar $paths->current_project_root,
        %{ $paths->named_paths },
    );
    print json_encode(\%out);
    exit 0;
}
elsif ( $cmd eq 'path' ) {
    my $action = shift @ARGV || '';
    if ( $action eq 'resolve' ) {
        _load_configured_path_aliases();
        my $name = shift @ARGV || die "Usage: dashboard path resolve <name>\n";
        print $paths->resolve_dir($name), "\n";
        exit 0;
    }
    elsif ( $action eq 'locate' ) {
        my @found = $paths->locate_projects(@ARGV);
        print json_encode( \@found );
        exit 0;
    }
    elsif ( $action eq 'add' ) {
        my $name = shift @ARGV || die "Usage: dashboard path add <name> <path>\n";
        my $path = shift @ARGV || die "Usage: dashboard path add <name> <path>\n";
        my $saved = $config->save_global_path_alias( $name, $path );
        $paths->register_named_paths( { $name => $path } );
        $saved->{resolved} = $paths->resolve_dir($name);
        print json_encode($saved);
        exit 0;
    }
    elsif ( $action eq 'del' ) {
        my $name = shift @ARGV || die "Usage: dashboard path del <name>\n";
        my $deleted = $config->remove_global_path_alias($name);
        $paths->unregister_named_path($name);
        print json_encode($deleted);
        exit 0;
    }
    elsif ( $action eq 'project-root' ) {
        my $root = $paths->current_project_root;
        print defined $root ? "$root\n" : '';
        exit 0;
    }
    elsif ( $action eq 'list' ) {
        _load_configured_path_aliases();
        my %out = (
            home         => $paths->home,
            home_runtime => $paths->home_runtime_root,
            project_runtime => scalar $paths->project_runtime_root,
            runtime      => $paths->runtime_root,
            state        => $paths->state_root,
            cache        => $paths->cache_root,
            logs         => $paths->logs_root,
            dashboards   => $paths->dashboards_root,
            bookmarks    => $paths->bookmarks_root,
            cli          => $paths->cli_root,
            config       => $paths->config_root,
            collectors   => $paths->collectors_root,
            indicators   => $paths->indicators_root,
            %{ $paths->named_paths },
        );
        print json_encode(\%out);
        exit 0;
    }
}
elsif ( $cmd eq 'encode' ) {
    local $/;
    my $text = <STDIN>;
    print encode_payload($text), "\n";
    exit 0;
}
elsif ( $cmd eq 'decode' ) {
    local $/;
    my $token = <STDIN>;
    $token =~ s/\s+$//;
    print decode_payload($token);
    exit 0;
}
elsif ( $cmd eq 'indicator' ) {
    _sync_configured_collector_indicators() if ( ( $ARGV[0] || '' ) eq 'list' );
    my $action = shift @ARGV || '';
    if ( $action eq 'set' ) {
        my ( $name, $label, $icon, $status ) = @ARGV;
        die "Usage: dashboard indicator set <name> <label> <icon> <status>\n" if !$status;
        my $item = $indicators->set_indicator(
            $name,
            label          => $label,
            icon           => $icon,
            status         => $status,
            priority       => 100,
            prompt_visible => 1,
        );
        print json_encode($item);
        exit 0;
    }
    elsif ( $action eq 'list' ) {
        print json_encode( [ $indicators->list_indicators ] );
        exit 0;
    }
    elsif ( $action eq 'refresh-core' ) {
        my $cwd = shift @ARGV || cwd();
        print json_encode( $indicators->refresh_core_indicators( cwd => $cwd ) );
        exit 0;
    }
}
elsif ( $cmd eq 'collector' ) {
    my $action = shift @ARGV || '';
    if ( $action eq 'write-result' ) {
        my ( $name, $exit_code ) = @ARGV;
        die "Usage: dashboard collector write-result <name> <exit_code>\n" if !defined $exit_code;
        local $/;
        my $stdout = <STDIN>;
        $collectors->write_result(
            $name,
            exit_code => $exit_code,
            stdout    => defined $stdout ? $stdout : '',
            stderr    => '',
        );
        exit 0;
    }
    elsif ( $action eq 'status' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector status <name>\n";
        print json_encode( $collectors->read_status($name) || {} );
        exit 0;
    }
    elsif ( $action eq 'list' ) {
        print json_encode( [ $collectors->list_collectors ] );
        exit 0;
    }
    elsif ( $action eq 'job' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector job <name>\n";
        print json_encode( $collectors->read_job($name) || {} );
        exit 0;
    }
    elsif ( $action eq 'output' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector output <name>\n";
        print json_encode( $collectors->read_output($name) || {} );
        exit 0;
    }
    elsif ( $action eq 'inspect' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector inspect <name>\n";
        print json_encode( $collectors->inspect_collector($name) || {} );
        exit 0;
    }
    elsif ( $action eq 'log' ) {
        print $files->read('collector_log') // '';
        exit 0;
    }
    elsif ( $action eq 'run' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector run <name>\n";
        my ($job) = grep { $_->{name} eq $name } @{ _collector_jobs() };
        die "Unknown collector '$name'\n" if !$job;
        print json_encode( $runner->run_once($job) );
        exit 0;
    }
    elsif ( $action eq 'start' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector start <name>\n";
        my ($job) = grep { $_->{name} eq $name } @{ _collector_jobs() };
        die "Unknown collector '$name'\n" if !$job;
        my $pid = $runner->start_loop($job);
        print "$pid\n";
        exit 0;
    }
    elsif ( $action eq 'stop' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector stop <name>\n";
        my $pid = $runner->stop_loop($name);
        print defined $pid ? "$pid\n" : '';
        exit 0;
    }
    elsif ( $action eq 'restart' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector restart <name>\n";
        my ($job) = grep { $_->{name} eq $name } @{ _collector_jobs() };
        die "Unknown collector '$name'\n" if !$job;
        $runner->stop_loop($name);
        my $pid = $runner->start_loop($job);
        print "$pid\n";
        exit 0;
    }
}
elsif ( $cmd eq 'config' ) {
    my $action = shift @ARGV || '';
    if ( $action eq 'init' ) {
        my $file = $config->save_global(
            {
                collectors => [
                    {
                        name     => 'example.collector',
                        command  => "printf 'example collector output\\n'",
                        cwd      => 'home',
                        interval => 60,
                    },
                ],
            }
        );
        print "$file\n";
        exit 0;
    }
    elsif ( $action eq 'show' ) {
        _load_configured_path_aliases();
        print json_encode( $config->merged );
        exit 0;
    }
}
elsif ( $cmd eq 'auth' ) {
    my $action = shift @ARGV || '';
    if ( $action eq 'add-user' ) {
        my ( $username, $password ) = @ARGV;
        die "Usage: dashboard auth add-user <username> <password>\n" if !$username || !$password;
        print json_encode(
            $auth->add_user(
                username => $username,
                password => $password,
            )
        );
        exit 0;
    }
    elsif ( $action eq 'list-users' ) {
        print json_encode( [ $auth->list_users ] );
        exit 0;
    }
    elsif ( $action eq 'remove-user' ) {
        my $username = shift @ARGV || die "Usage: dashboard auth remove-user <username>\n";
        $auth->remove_user($username);
        print json_encode( { removed => $username } );
        exit 0;
    }
}
elsif ( $cmd eq 'init' ) {
    _migrate_saved_pages();
    my $migrated = [];
    my $config_file = $config->save_global(
        {
            collectors => [
                {
                    name     => 'example.collector',
                    command  => "printf 'example collector output\\n'",
                    cwd      => 'home',
                    interval => 60,
                },
            ],
        }
    );

    my @pages = $pages->list_saved_pages;
    _seed_init_page( $pages, \@pages, _welcome_page() );
    _seed_init_page( $pages, \@pages, _api_dashboard_page() );
    _seed_init_page( $pages, \@pages, _sql_dashboard_page() );
    my $internal_cli = Developer::Dashboard::InternalCLI::ensure_helpers( paths => $paths );

    print json_encode(
        {
            config_file => $config_file,
            internal_cli => $internal_cli,
            runtime_root => $paths->runtime_root,
            migrated_pages => $migrated,
            pages        => [ $pages->list_saved_pages ],
        }
    );
    exit 0;
}
elsif ( $cmd eq 'cpan' ) {
    my @modules = @ARGV;
    my $result = eval {
        _install_runtime_cpan_modules(
            paths   => $paths,
            modules => \@modules,
        );
    };
    if ($@) {
        my $error = $@;
        $error =~ s/\s+\z//;
        die "$error\n";
    }
    die $result->{error} . "\n" if $result->{error};
    print json_encode($result);
    exit 0;
}
elsif ( $cmd eq 'version' ) {
    print $Developer::Dashboard::VERSION, "\n";
    exit 0;
}
elsif ( $cmd eq 'page' ) {
    _load_configured_path_aliases();
    _migrate_saved_pages();
    my $action = shift @ARGV || '';
    if ( $action eq 'new' ) {
        my $id = shift @ARGV || '';
        my $title = shift @ARGV || 'Untitled';
        my $page = Developer::Dashboard::PageDocument->new(
            id          => $id || undef,
            title       => $title,
            description => 'Project-neutral Developer Dashboard page',
            layout      => {
                body => "Replace this body with your own page content.",
            },
            state => {
                project => '',
            },
            actions => [
                { id => 'example', label => 'Example Action' },
            ],
        );
        print $page->canonical_instruction;
        exit 0;
    }
    elsif ( $action eq 'save' ) {
        my $id = shift @ARGV || die "Usage: dashboard page save <id>\n";
        local $/;
        my $source = <STDIN>;
        my $page = $source =~ /^\s*\{/
          ? Developer::Dashboard::PageDocument->from_json($source)
          : Developer::Dashboard::PageDocument->from_instruction($source);
        $page->{id} = $id;
        my $file = $pages->save_page($page);
        print "$file\n";
        exit 0;
    }
    elsif ( $action eq 'list' ) {
        print json_encode( [ $resolver->list_pages ] );
        exit 0;
    }
    elsif ( $action eq 'show' ) {
        my $id = shift @ARGV || die "Usage: dashboard page show <id>\n";
        my $page = $resolver->load_named_page($id);
        print $page->canonical_instruction;
        exit 0;
    }
    elsif ( $action eq 'encode' ) {
        my $id = shift @ARGV;
        my $page;
        if ($id) {
            $page = $pages->load_saved_page($id);
        }
        else {
            local $/;
            my $source = <STDIN>;
            $page = $source =~ /^\s*\{/
              ? Developer::Dashboard::PageDocument->from_json($source)
              : Developer::Dashboard::PageDocument->from_instruction($source);
        }
        print $pages->encode_page($page), "\n";
        exit 0;
    }
    elsif ( $action eq 'decode' ) {
        my $token = shift @ARGV || do {
            local $/;
            scalar <STDIN>;
        };
        $token =~ s/\s+$// if defined $token;
        my $page = $pages->load_transient_page($token);
        print $page->canonical_instruction;
        exit 0;
    }
    elsif ( $action eq 'urls' ) {
        my $id = shift @ARGV || die "Usage: dashboard page urls <id>\n";
        my $page = $pages->load_saved_page($id);
        print json_encode(
            {
                edit   => $pages->editable_url($page),
                render => $pages->render_url($page),
                source => $pages->source_url($page),
            }
        );
        exit 0;
    }
    elsif ( $action eq 'render' ) {
        my $source = shift @ARGV || '';
        my $page;
        if ($source) {
            if ( -f $source ) {
                open my $fh, '<', $source or die "Unable to read $source: $!";
                local $/;
                my $raw = <$fh>;
                $page = $raw =~ /^\s*\{/
                  ? Developer::Dashboard::PageDocument->from_json($raw)
                  : Developer::Dashboard::PageDocument->from_instruction($raw);
            }
            else {
                $page = $resolver->load_named_page($source);
            }
        }
        else {
            local $/;
            my $source = <STDIN>;
            $page = $source =~ /^\s*\{/
              ? Developer::Dashboard::PageDocument->from_json($source)
              : Developer::Dashboard::PageDocument->from_instruction($source);
        }
        $page->with_mode('render');
        print $page->render_html;
        exit 0;
    }
    elsif ( $action eq 'source' ) {
        my $source = shift @ARGV || die "Usage: dashboard page source <id|token>\n";
        my $page = eval { $resolver->load_named_page($source) };
        if ( !$page ) {
            die $@ if $source !~ /^[A-Za-z0-9+\/=]+$/;
            $page = $pages->load_transient_page($source);
        }
        $page->with_mode('source');
        print $page->canonical_instruction;
        exit 0;
    }
}
elsif ( $cmd eq 'action' ) {
    _load_configured_path_aliases();
    _migrate_saved_pages();
    my $sub = shift @ARGV || '';
    if ( $sub eq 'run' ) {
        my $page_id = shift @ARGV || die "Usage: dashboard action run <page_id> <action_id>\n";
        my $action_id = shift @ARGV || die "Usage: dashboard action run <page_id> <action_id>\n";
        my $page = $resolver->load_named_page($page_id);
        my ($action) = grep { ref($_) eq 'HASH' && ( $_->{id} || '' ) eq $action_id } @{ $page->as_hash->{actions} || [] };
        die "Unknown action '$action_id'\n" if !$action;
        print json_encode(
            $actions->run_page_action(
                action => $action,
                page   => $page,
                source => $page->{meta}{source_kind} || 'saved',
            )
        );
        exit 0;
    }
}
elsif ( $cmd eq 'docker' ) {
    _load_configured_path_aliases();
    my $sub = shift @ARGV || '';
    if ( $sub eq 'compose' ) {
        my @addons;
        my @modes;
        my @services;
        my $project_root = '';
        my $dry_run = 0;
        Getopt::Long::Configure(qw(pass_through no_getopt_compat no_auto_abbrev));
        GetOptionsFromArray(
            \@ARGV,
            'addon=s@'   => \@addons,
            'mode=s@'    => \@modes,
            'service=s@' => \@services,
            'project=s'  => \$project_root,
            'dry-run!'   => \$dry_run,
        );
        Getopt::Long::Configure(qw(no_pass_through getopt_compat auto_abbrev));
        my $result = $docker->resolve(
            addons       => \@addons,
            args         => \@ARGV,
            modes        => \@modes,
            services     => \@services,
            project_root => $project_root || undef,
        );
        if ($dry_run) {
            print json_encode($result);
            exit 0;
        }
        chdir $result->{project_root} or die "Unable to chdir to $result->{project_root}: $!";
        local @ENV{ keys %{ $result->{env} } } = values %{ $result->{env} } if %{ $result->{env} };
        exec @{ $result->{command} };
        die "Unable to exec docker compose: $!";
    }
}
elsif ( $cmd eq 'serve' ) {
    _load_configured_path_aliases();
    _migrate_saved_pages();
    my $action = @ARGV && $ARGV[0] !~ /^-/ ? shift @ARGV : '';
    if ( $action eq 'logs' ) {
        my $follow = 0;
        my $lines;
        GetOptionsFromArray(
            \@ARGV,
            'f'   => \$follow,
            'n=i' => \$lines,
        );
        print $runtime->web_log(
            follow => $follow,
            ( defined $lines ? ( lines => $lines ) : () ),
        );
        exit 0;
    }
    if ( $action eq 'workers' ) {
        my $workers = shift @ARGV;
        my $host = '0.0.0.0';
        my $port = 7890;
        GetOptionsFromArray(
            \@ARGV,
            'host=s' => \$host,
            'port=i' => \$port,
        );
        my $saved = $config->save_global_web_workers($workers);
        my $running = $runtime->running_web;
        my $pid;
        if ( !$running ) {
            $pid = $runtime->start_web(
                host    => $host,
                port    => $port,
                workers => $saved->{workers},
            );
        }
        print json_encode(
            {
                %{$saved},
                ( defined $pid ? ( pid => $pid ) : () ),
            }
        );
        exit 0;
    }
    unshift @ARGV, $action if defined $action && $action ne '';
    my $settings = $config->web_settings;
    my $host = $settings->{host};
    my $port = $settings->{port};
    my $workers = $settings->{workers};
    my $ssl = $settings->{ssl};
    my $foreground = 0;
    GetOptionsFromArray(
        \@ARGV,
        'host=s'      => \$host,
        'port=i'      => \$port,
        'workers=i'   => \$workers,
        'ssl!'        => \$ssl,
        'foreground!' => \$foreground,
    );
    $config->save_global_web_settings(
        host    => $host,
        port    => $port,
        workers => $workers,
        ssl     => $ssl,
    );
    my $result = $runtime->start_web(
        foreground => $foreground,
        host       => $host,
        port       => $port,
        workers    => $workers,
        ssl        => $ssl,
    );
    if (!$foreground) {
        print json_encode(
            {
                host    => $host,
                port    => $port,
                workers => $workers,
                ssl     => $ssl,
                pid     => $result,
            }
        );
    }
    exit 0;
}
elsif ( $cmd eq 'stop' ) {
    print json_encode( $runtime->stop_all );
    exit 0;
}
elsif ( $cmd eq 'restart' ) {
    _load_configured_path_aliases();
    my $settings = $config->web_settings;
    my $host = $settings->{host};
    my $port = $settings->{port};
    my $workers = $settings->{workers};
    my $ssl = $settings->{ssl};
    GetOptionsFromArray(
        \@ARGV,
        'host=s'    => \$host,
        'port=i'    => \$port,
        'workers=i' => \$workers,
        'ssl!'      => \$ssl,
    );
    print json_encode(
        $runtime->restart_all(
            host    => $host,
            port    => $port,
            workers => $workers,
            ssl     => $ssl,
        )
    );
    exit 0;
}
elsif ( $cmd eq 'shell' ) {
    my $shell = normalize_shell_name( shift @ARGV || native_shell_name() );
    print _shell_bootstrap($shell);
    exit 0;
}
elsif ( $cmd eq 'doctor' ) {
    my $fix = 0;
    GetOptionsFromArray(
        \@ARGV,
        'fix!' => \$fix,
    );
    print json_encode(
        $doctor->run(
            fix => $fix,
        )
    );
    exit 0;
}
elsif ( $cmd eq 'skills' ) {
    require Developer::Dashboard::SkillManager;
    my $skill_mgr = Developer::Dashboard::SkillManager->new();
    
    my $action = shift @ARGV || '';
    
    if ( $action eq 'install' ) {
        my $git_url = shift @ARGV || '';
        die "Usage: dashboard skills install <git-url>\n" if !$git_url;
        my $result = $skill_mgr->install($git_url);
        print json_encode($result);
        exit( $result->{error} ? 1 : 0 );
    }
    elsif ( $action eq 'uninstall' ) {
        my $repo_name = shift @ARGV || '';
        die "Usage: dashboard skills uninstall <repo-name>\n" if !$repo_name;
        my $result = $skill_mgr->uninstall($repo_name);
        print json_encode($result);
        exit( $result->{error} ? 1 : 0 );
    }
    elsif ( $action eq 'update' ) {
        my $repo_name = shift @ARGV || '';
        die "Usage: dashboard skills update <repo-name>\n" if !$repo_name;
        my $result = $skill_mgr->update($repo_name);
        print json_encode($result);
        exit( $result->{error} ? 1 : 0 );
    }
    elsif ( $action eq 'list' ) {
        my $skills = $skill_mgr->list();
        print json_encode({ skills => $skills });
        exit 0;
    }
    else {
        die "Unknown skills action: $action\nUsage: dashboard skills [install|uninstall|update|list]\n";
    }
}
elsif ( $cmd eq 'skill' ) {
    require Developer::Dashboard::SkillDispatcher;
    my $dispatcher = Developer::Dashboard::SkillDispatcher->new();
    
    my $skill_name = shift @ARGV || '';
    my $skill_cmd = shift @ARGV || '';
    
    die "Usage: dashboard skill <skill-name> <command> [args...]\n" if !$skill_name || !$skill_cmd;
    
    my $result = $dispatcher->dispatch( $skill_name, $skill_cmd, @ARGV );
    
    if ( $result->{error} ) {
        print STDERR $result->{error}, "\n";
        exit 1;
    }
    
    print $result->{stdout} if $result->{stdout};
    print STDERR $result->{stderr} if $result->{stderr};
    exit( $result->{exit_code} || 0 );
}
else {
    my $user_cli = _custom_command_path($cmd);
    if ( -d $user_cli ) {
        my $run = _resolve_directory_runner($user_cli);
        if ($run) {
            my @command = command_argv_for_path($run);
            exec { $command[0] } @command, @ARGV;
            die "Unable to exec $run: $!";
        }
    }
    if ( my $command_path = resolve_runnable_file($user_cli) ) {
        my @command = command_argv_for_path($command_path);
        exec { $command[0] } @command, @ARGV;
        die "Unable to exec $command_path: $!";
    }
}

# _prime_command_result_env($cmd, @argv)
# Runs executable command hook files from ~/.developer-dashboard/cli/<cmd> and
# exports their captured outputs as RESULT JSON for later hooks and the final
# command implementation.
# Input: top-level command name plus the remaining argv list.
# Output: true when hook processing completes.
sub _prime_command_result_env {
    my ( $cmd, @argv ) = @_;
    delete $ENV{RESULT};
    $ENV{DEVELOPER_DASHBOARD_COMMAND} = $cmd if defined $cmd && $cmd ne '';
    return 1 if !defined $cmd || $cmd eq '';

    my $hook_root = _command_hook_root($cmd);
    return 1 if !-d $hook_root;
    my $stream = $cmd eq 'doctor' ? 0 : 1;

    opendir my $dh, $hook_root or return 1;
    my %results;
    for my $entry ( sort grep { $_ ne '.' && $_ ne '..' } readdir $dh ) {
        my $path = File::Spec->catfile( $hook_root, $entry );
        next if !is_runnable_file($path);
        next if $entry eq 'run';

        my $hook_result = _run_command_hook_streaming( $path, stream => $stream, argv => \@argv );
        $results{$entry} = {
            stdout => $hook_result->{stdout},
            stderr => $hook_result->{stderr},
        };
        $results{$entry}{exit_code} = $hook_result->{exit_code} if defined $hook_result->{exit_code};
        $ENV{RESULT} = JSON::XS->new->canonical->encode( \%results );
    }
    closedir $dh;

    delete $ENV{RESULT} if !%results;
    return 1;
}

# _run_command_hook_streaming($path, @argv)
# Runs one executable hook file, streams its stdout/stderr live, and captures
# both channels for RESULT JSON propagation.
# Input: executable path plus remaining dashboard argv list.
# Output: hash reference with stdout, stderr, and exit_code.
sub _run_command_hook_streaming {
    my ( $path, %args ) = @_;
    my $stream = exists $args{stream} ? $args{stream} : 1;
    my @argv = @{ $args{argv} || [] };
    open my $stdin, '<', File::Spec->devnull() or die "Unable to open " . File::Spec->devnull() . " for hook stdin: $!";
    my $stderr = gensym();
    my $stdout;
    my @command = command_argv_for_path($path);
    my $pid = open3( $stdin, $stdout, $stderr, @command, @argv );
    close $stdin;

    my $selector = IO::Select->new( $stdout, $stderr );
    my $stdout_fd = fileno($stdout);
    my $stderr_fd = fileno($stderr);
    my $stdout_text = '';
    my $stderr_text = '';
    local $| = 1;
    STDOUT->autoflush(1);
    STDERR->autoflush(1);

    while ( my @ready = $selector->can_read ) {
        for my $fh (@ready) {
            my $buffer = '';
            my $read = sysread( $fh, $buffer, 8192 );
            if ( !defined $read || $read == 0 ) {
                $selector->remove($fh);
                close $fh;
                next;
            }

            if ( fileno($fh) == $stdout_fd ) {
                print STDOUT $buffer if $stream;
                $stdout_text .= $buffer;
                next;
            }

            if ( fileno($fh) == $stderr_fd ) {
                print STDERR $buffer if $stream;
                $stderr_text .= $buffer;
                next;
            }
        }
    }

    waitpid( $pid, 0 );
    return {
        stdout    => $stdout_text,
        stderr    => $stderr_text,
        exit_code => $? >> 8,
    };
}

# _command_hook_root($cmd)
# Resolves the per-command hook directory under the runtime CLI extension root.
# Input: top-level command name string.
# Output: directory path string, preferring <command>/ over <command>.d/.
sub _command_hook_root {
    my ($cmd) = @_;
    return '' if !defined $cmd || $cmd eq '';
    for my $root ( _cli_runtime_roots() ) {
        my $plain_root = File::Spec->catdir( $root, $cmd );
        return $plain_root if -d $plain_root;
        my $d_root = File::Spec->catdir( $root, $cmd . '.d' );
        return $d_root if -d $d_root;
    }
    return File::Spec->catdir( $ENV{HOME}, '.developer-dashboard', 'cli', $cmd );
}

# _custom_command_path($cmd)
# Resolves the effective custom command path from project-local and home CLI roots.
# Input: top-level command name string.
# Output: file or directory path string.
sub _custom_command_path {
    my ($cmd) = @_;
    return '' if !defined $cmd || $cmd eq '';
    if ( my $helper = Developer::Dashboard::InternalCLI::canonical_helper_name($cmd) ) {
        require Developer::Dashboard::PathRegistry;
        my $paths = Developer::Dashboard::PathRegistry->new(
            home            => $ENV{HOME},
            workspace_roots => [],
            project_roots   => [],
        );
        Developer::Dashboard::InternalCLI::ensure_helpers( paths => $paths );
        return Developer::Dashboard::InternalCLI::helper_path( paths => $paths, name => $helper );
    }
    for my $root ( _cli_runtime_roots() ) {
        my $path = File::Spec->catfile( $root, $cmd );
        return $path if -e $path;
    }
    return File::Spec->catfile( $ENV{HOME}, '.developer-dashboard', 'cli', $cmd );
}

# _cli_runtime_roots()
# Returns project-local then home CLI roots for top-level command resolution before the heavy runtime loads.
# Input: none.
# Output: ordered list of CLI root directory path strings.
sub _cli_runtime_roots {
    my @roots;
    my %seen;
    my $cwd = cwd();
    my $project_root = _project_root_for($cwd);
    for my $root (
        ( defined $project_root && -d File::Spec->catdir( $project_root, '.developer-dashboard' )
            ? File::Spec->catdir( $project_root, '.developer-dashboard', 'cli' )
            : () ),
        File::Spec->catdir( $ENV{HOME}, '.developer-dashboard', 'cli' ),
      )
    {
        next if !defined $root || $root eq '';
        next if $seen{$root}++;
        push @roots, $root;
    }
    return @roots;
}

# _project_root_for($start_dir)
# Resolves the nearest project root containing a .git directory.
# Input: starting directory path string.
# Output: project root directory path string or undef.
sub _project_root_for {
    my ($dir) = @_;
    while ($dir) {
        return $dir if -d File::Spec->catdir( $dir, '.git' );
        my $parent = File::Spec->catdir( $dir, File::Spec->updir );
        $parent = Cwd::abs_path($parent) if $parent ne $dir;
        last if !$parent || $parent eq $dir;
        $dir = $parent;
    }
    return;
}

# _seed_init_page($pages, $known_ids, $page)
# Saves one seeded page when it does not already exist in the effective bookmark roots.
# Input: page store, array reference of known ids, and page document object.
# Output: true when the page already existed or was written.
sub _seed_init_page {
    my ( $pages, $known_ids, $page ) = @_;
    my $id = $page->as_hash->{id} || return 1;
    return 1 if grep { $_ eq $id } @{ $known_ids || [] };
    $pages->save_page($page);
    push @{ $known_ids || [] }, $id;
    return 1;
}

# _welcome_page()
# Builds the seeded welcome bookmark page for dashboard init.
# Input: none.
# Output: Developer::Dashboard::PageDocument object.
sub _welcome_page {
    return Developer::Dashboard::PageDocument->new(
        id          => 'welcome',
        title       => 'Welcome to Developer Dashboard',
        description => 'A project-neutral local dashboard starter page.',
        layout      => {
            body => "Developer Dashboard is ready.\n\nUse dashboard page new/save to create more pages.\nUse dashboard serve to browse them.\nUse dashboard collector run to refresh prepared data.\nUse dashboard ps1 from your shell to render prompt status.",
        },
        state => {
            project => '',
        },
        actions => [
            { id => 'serve', label => 'Run dashboard serve' },
            { id => 'ps1',   label => 'Use dashboard ps1 in your shell' },
        ],
    );
}

# _api_dashboard_page()
# Builds the seeded API dashboard bookmark page inspired by the older request workspace without carrying forward sensitive defaults.
# Input: none.
# Output: Developer::Dashboard::PageDocument object.
sub _api_dashboard_page {
    return Developer::Dashboard::PageDocument->from_instruction(<<'BOOKMARK');
TITLE: API Dashboard
:--------------------------------------------------------------------------------:
BOOKMARK: api-dashboard
:--------------------------------------------------------------------------------:
NOTE: A Postman-style request workspace with collection import/export, multiple request tabs, and bookmark-backed request dispatch.
:--------------------------------------------------------------------------------:
HTML: <script src="/js/jquery.js"></script>
<style>
.api-postman-shell { display:grid; gap:18px; }
.api-card { border:1px solid #d9d3c7; background:linear-gradient(180deg, #fffaf1 0%, #fffdf8 100%); border-radius:18px; box-shadow:0 10px 30px rgba(31,42,46,0.08); overflow:hidden; }
.api-card h2, .api-card h3 { margin:0; }
.api-sidebar-header, .api-main-toolbar { padding:16px 18px; border-bottom:1px solid #e6ddcf; background:#f7efe0; }
.api-sidebar-body, .api-main-body { padding:18px; }
.api-shell-tabs, .api-response-tabs, .api-collection-tabs, .api-tab-strip { display:flex; gap:6px; flex-wrap:wrap; align-items:flex-end; border-bottom:1px solid #d8ccb8; }
.api-shell-tabs { padding:0 12px; }
.api-response-tabs, .api-collection-tabs, .api-tab-strip { margin:0; }
.api-shell-tab, .api-response-tab, .api-collection-tab, .api-tab {
  display:inline-flex;
  align-items:center;
  gap:8px;
  border:1px solid #d1b783;
  border-bottom:0;
  border-radius:12px 12px 0 0;
  background:#efe3cd;
  color:#6a4512;
  cursor:pointer;
  font-weight:700;
  padding:10px 16px;
  margin:0 0 -1px;
}
.api-shell-tab[aria-selected="true"], .api-response-tab[aria-selected="true"], .api-collection-tab[aria-selected="true"], .api-tab.is-active {
  background:#fffdf8;
  border-color:#d8ccb8;
  color:#1f2a2e;
}
.api-shell-panel[hidden], .api-response-panel[hidden] { display:none !important; }
.api-toolbar-group { display:flex; gap:10px; flex-wrap:wrap; }
.api-toolbar-group button, .api-request-actions button { border:1px solid #d1b783; border-radius:999px; background:#fff7e7; color:#6a4512; cursor:pointer; font-weight:600; padding:9px 14px; }
.api-toolbar-group button:hover, .api-request-actions button:hover { background:#f7e1af; }
.api-toolbar-group input[type=file] { color:#6a4512; font-weight:600; max-width:280px; }
.api-toolbar-group input[type=file]::file-selector-button { border:1px solid #d1b783; border-radius:999px; background:#fff7e7; color:#6a4512; cursor:pointer; font-weight:600; padding:9px 14px; margin-right:10px; }
.api-toolbar-group input[type=file]::file-selector-button:hover { background:#f7e1af; }
.api-collection-tree { display:grid; gap:12px; }
.api-collection-card { border:1px solid #eadfca; border-radius:0 14px 14px 14px; padding:14px; background:#fffdf8; }
.api-collection-card.is-active { border-color:#0b7a75; box-shadow:0 0 0 2px rgba(11,122,117,0.10); }
.api-collection-card h3 { font-size:1rem; margin-bottom:8px; }
.api-node-list { list-style:none; margin:0; padding:0; display:grid; gap:6px; }
.api-node-list ul { list-style:none; margin:8px 0 0 14px; padding:0 0 0 10px; display:grid; gap:6px; border-left:1px dashed #d8ccb8; }
.api-node-button { width:100%; text-align:left; border:1px solid #e9dec8; border-radius:10px; background:#fff; padding:8px 10px; cursor:pointer; color:#1f2a2e; }
.api-node-button:hover { border-color:#0b7a75; color:#0b7a75; }
.api-node-button.is-active { border-color:#0b7a75; background:#e7f5f4; color:#0b7a75; font-weight:700; }
.api-node-folder { font-weight:700; background:#f8f1e4; }
.api-empty { color:#6a767b; font-style:italic; }
.api-location { margin:0 0 12px; padding:10px 12px; border:1px dashed #d8ccb8; border-radius:12px; background:#fffdf8; color:#6a4512; font-size:0.92rem; line-height:1.5; }
.api-tab-close { border:0; background:transparent; color:inherit; cursor:pointer; padding:0; font-size:1rem; line-height:1; }
.api-editor-grid { display:grid; gap:14px; grid-template-columns:repeat(2, minmax(0, 1fr)); }
.api-field { display:grid; gap:6px; }
.api-field label { font-weight:700; color:#6a4512; }
.api-field input, .api-field select, .api-field textarea { width:100%; box-sizing:border-box; padding:10px 12px; border:1px solid #d8ccb8; border-radius:12px; font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:0.95rem; background:#fffefb; }
.api-field textarea { min-height:140px; resize:vertical; }
.api-field-full { grid-column:1 / -1; }
.api-token-shell { border:1px solid #e6ddcf; border-radius:14px; padding:12px; background:#fffaf1; }
.api-token-help { margin:0; color:#6a767b; font-size:0.9rem; }
.api-token-fields { display:grid; gap:10px; grid-template-columns:repeat(auto-fit, minmax(240px, 1fr)); margin-top:12px; }
.api-token-field { display:grid; gap:6px; }
.api-token-field input { width:100%; box-sizing:border-box; padding:10px 12px; border:1px solid #d8ccb8; border-radius:12px; font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:0.95rem; background:#fffefb; }
.api-inline-toggles { display:flex; gap:14px; flex-wrap:wrap; align-items:center; }
.api-inline-toggles label { font-weight:600; color:#1f2a2e; display:inline-flex; gap:6px; align-items:center; }
.api-request-actions { display:flex; gap:10px; flex-wrap:wrap; }
.api-response-shell { display:grid; gap:0; }
.api-response-panel { padding:0; }
.api-response-box { border:1px solid #d8ccb8; border-radius:14px 14px 0 0; overflow:hidden; background:#fffdf8; }
.api-response-box header { padding:10px 14px; background:#f7efe0; font-weight:700; color:#6a4512; border-bottom:1px solid #e6ddcf; }
.api-response-box pre { margin:0; padding:14px; min-height:220px; white-space:pre-wrap; overflow:auto; background:#1f2a2e; color:#f4efe3; }
.api-response-preview { display:none; padding:14px; min-height:220px; background:#f4f1eb; border-top:1px solid #e6ddcf; }
.api-response-preview.is-visible { display:block; }
.api-response-preview img, .api-response-preview iframe, .api-response-preview object { width:100%; max-width:100%; min-height:220px; border:0; border-radius:12px; background:#fff; }
.api-response-meta { display:flex; gap:10px; flex-wrap:wrap; }
.api-chip { display:inline-flex; align-items:center; gap:6px; padding:6px 10px; border-radius:999px; background:#e6f4f3; color:#0b7a75; font-weight:700; }
.api-chip.is-error { background:#fde9e9; color:#9f2020; }
.api-banner { border:1px solid #d1b783; border-radius:14px; background:#fff6e2; color:#6a4512; padding:12px 14px; display:none; margin-bottom:12px; }
@media (max-width: 980px) {
  .api-editor-grid { grid-template-columns:1fr; }
}
</style>
<div class="api-postman-shell">
  <div class="api-shell-tabs" role="tablist" aria-label="API Dashboard Sections">
    <button type="button" id="api-shell-tab-collections" class="api-shell-tab" role="tab" aria-selected="false" aria-controls="api-shell-panel-collections" data-api-shell-tab="collections">Collections</button>
    <button type="button" id="api-shell-tab-workspace" class="api-shell-tab" role="tab" aria-selected="false" aria-controls="api-shell-panel-workspace" data-api-shell-tab="workspace">Workspace</button>
  </div>
  <aside class="api-card api-shell-panel" id="api-shell-panel-collections" role="tabpanel" aria-labelledby="api-shell-tab-collections" data-api-shell-panel="collections">
    <div class="api-sidebar-header">
      <h2>Collections</h2>
      <p style="margin:8px 0 0;color:#6a767b">Import and export Postman collection JSON, then open requests in local tabs.</p>
    </div>
    <div class="api-sidebar-body">
      <div class="api-toolbar-group" style="margin-bottom:14px">
        <button type="button" id="api-new-collection">New Collection</button>
        <button type="button" id="api-rename-collection">Rename Collection</button>
        <button type="button" id="api-delete-collection">Delete Collection</button>
        <button type="button" id="api-export-collection">Export Postman Collection</button>
        <input id="api-import-file" type="file" accept=".json,application/json" aria-label="Import Postman Collection">
      </div>
      <div id="api-banner" class="api-banner"></div>
      <div id="api-collection-location" class="api-location">Current Location: Workspace root</div>
      <div id="api-collection-tree" class="api-collection-tree"></div>
    </div>
  </aside>
  <section class="api-card api-shell-panel" id="api-shell-panel-workspace" role="tabpanel" aria-labelledby="api-shell-tab-workspace" data-api-shell-panel="workspace">
    <div class="api-main-toolbar">
      <h2>Workspace</h2>
      <p style="margin:8px 0 0;color:#6a767b">Use multiple tabs to compare requests, save useful calls into collections, and dispatch through the bookmark Ajax sender.</p>
    </div>
    <div class="api-main-body">
      <div class="api-toolbar-group">
        <button type="button" id="api-new-tab">New Tab</button>
        <button type="button" id="api-duplicate-tab">Duplicate Tab</button>
        <button type="button" id="api-save-request">Save Request To Collection</button>
        <button type="button" id="api-delete-request">Delete Saved Request</button>
      </div>
      <div id="api-tab-strip" class="api-tab-strip"></div>
      <div class="api-editor-grid">
        <div class="api-field">
          <label for="api-request-name">Request Name</label>
          <input id="api-request-name" placeholder="List Orders">
        </div>
        <div class="api-field">
          <label for="api-collection-name">Selected Collection</label>
          <input id="api-collection-name" placeholder="Scratch Pad" readonly>
        </div>
        <div class="api-field">
          <label for="api-request-method">Method</label>
          <select id="api-request-method">
            <option>GET</option>
            <option>POST</option>
            <option>PUT</option>
            <option>PATCH</option>
            <option>DELETE</option>
            <option>HEAD</option>
            <option>OPTIONS</option>
          </select>
        </div>
        <div class="api-field">
          <label for="api-timeout">Timeout (seconds)</label>
          <input id="api-timeout" type="number" min="1" max="600" value="120">
        </div>
        <div class="api-field api-field-full api-token-shell" id="api-token-shell" hidden>
          <label for="api-token-fields">Request Token Values</label>
          <p class="api-token-help">Fill placeholders used by the selected request. Values are shared across the active collection and carry over to matching tokens in other requests from the same collection.</p>
          <div id="api-token-fields" class="api-token-fields"></div>
        </div>
        <div class="api-field api-field-full">
          <label for="api-request-url">URL</label>
          <input id="api-request-url" placeholder="https://example.test/api/orders">
        </div>
        <div class="api-field api-field-full">
          <label for="api-request-variables">Collection Variables</label>
          <textarea id="api-request-variables" placeholder="base_url=https://example.test&#10;token=abc123"></textarea>
        </div>
        <div class="api-field api-field-full">
          <label for="api-request-headers">Headers</label>
          <textarea id="api-request-headers" placeholder="Accept: application/json&#10;Authorization: Bearer {{token}}"></textarea>
        </div>
        <div class="api-field api-field-full">
          <label for="api-request-body">Body</label>
          <textarea id="api-request-body" placeholder='{"status":"open"}'></textarea>
        </div>
        <div class="api-field api-field-full">
          <label for="api-request-description">Description</label>
          <textarea id="api-request-description" placeholder="Request notes or Postman collection description."></textarea>
        </div>
        <div class="api-field api-field-full">
          <div class="api-inline-toggles">
            <label><input id="api-follow-redirects" type="checkbox" checked> Follow redirects</label>
            <label><input id="api-insecure-tls" type="checkbox"> Allow insecure TLS</label>
          </div>
        </div>
      </div>
      <div class="api-request-actions">
        <button type="button" id="api-send-request">Send Request</button>
        <button type="button" id="api-clear-response">Clear Response</button>
      </div>
      <div id="api-response-meta" class="api-response-meta"></div>
      <div class="api-response-shell">
        <section class="api-response-box">
          <section class="api-response-panel" id="api-response-panel-request" role="tabpanel" aria-labelledby="api-response-tab-request" data-api-response-panel="request">
            <header>Request Details</header>
            <pre id="api-request-details">Ready.</pre>
          </section>
          <section class="api-response-panel" id="api-response-panel-body" role="tabpanel" aria-labelledby="api-response-tab-body" data-api-response-panel="body">
            <header>Response Body</header>
            <div id="api-response-preview" class="api-response-preview"></div>
            <pre id="api-response-body">Ready.</pre>
          </section>
          <section class="api-response-panel" id="api-response-panel-headers" role="tabpanel" aria-labelledby="api-response-tab-headers" data-api-response-panel="headers">
            <header>Response Headers</header>
            <pre id="api-response-headers">Ready.</pre>
          </section>
        </section>
        <div class="api-response-tabs" role="tablist" aria-label="API Response Sections">
          <button type="button" id="api-response-tab-request" class="api-response-tab" role="tab" aria-selected="false" aria-controls="api-response-panel-request" data-api-response-tab="request">Request Details</button>
          <button type="button" id="api-response-tab-body" class="api-response-tab" role="tab" aria-selected="false" aria-controls="api-response-panel-body" data-api-response-tab="body">Response Body</button>
          <button type="button" id="api-response-tab-headers" class="api-response-tab" role="tab" aria-selected="false" aria-controls="api-response-panel-headers" data-api-response-tab="headers">Response Headers</button>
        </div>
      </div>
    </div>
  </section>
</div>
<script>
var configs = {};
(function () {
  var STORAGE_KEY = 'developer-dashboard:api-dashboard:v6';
  var POSTMAN_SCHEMA = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json';
  var state = null;

  // uid(prefix)
  // Purpose: create stable-looking local identifiers for collections, tabs, and requests.
  // Input: optional string prefix.
  // Output: string identifier.
  function uid(prefix) {
    return [prefix || 'id', Date.now().toString(36), Math.random().toString(36).slice(2, 10)].join('-');
  }

  // clone(value)
  // Purpose: deep-clone plain JSON-safe state objects.
  // Input: JSON-safe value.
  // Output: cloned value.
  function clone(value) {
    return JSON.parse(JSON.stringify(value));
  }

  // showBanner(message, isError)
  // Purpose: render explicit success or error banners for workspace actions.
  // Input: message string and boolean error flag.
  // Output: none.
  function showBanner(message, isError) {
    var banner = document.getElementById('api-banner');
    banner.textContent = message || '';
    banner.style.display = message ? 'block' : 'none';
    banner.style.borderColor = isError ? '#e3b0b0' : '#d1b783';
    banner.style.background = isError ? '#fff0f0' : '#fff6e2';
    banner.style.color = isError ? '#9f2020' : '#6a4512';
  }

  // defaultRequest(name)
  // Purpose: build a new empty request model for a tab or collection item.
  // Input: optional request name.
  // Output: request hash.
  function defaultRequest(name) {
    return {
      name: name || 'New Request',
      method: 'GET',
      url: 'https://example.test/api',
      headers_text: 'Accept: application/json',
      body: '',
      description: '',
      timeout_s: 120,
      insecure_tls: false,
      follow_redirects: true
    };
  }

  // scratchCollection()
  // Purpose: seed a safe local collection when no saved collections exist.
  // Input: none.
  // Output: collection hash.
  function scratchCollection() {
    return {
      id: uid('collection'),
      name: 'Scratch Pad',
      description: 'A local scratch collection for ad-hoc API calls.',
      variable: [
        { key: 'base_url', value: 'https://example.test' },
        { key: 'token', value: '' }
      ],
      item: [
        {
          id: uid('request'),
          kind: 'request',
          name: 'Example GET',
          request: defaultRequest('Example GET')
        }
      ]
    };
  }

  // normalizeCollectionVariable(variable)
  // Purpose: sanitize one collection variable entry.
  // Input: candidate variable object.
  // Output: normalized variable hash or null.
  function normalizeCollectionVariable(variable) {
    if (!variable || typeof variable !== 'object') return null;
    var key = String(variable.key || '').trim();
    if (!key) return null;
    return {
      key: key,
      value: variable.value == null ? '' : String(variable.value)
    };
  }

  // normalizeResponse(input)
  // Purpose: normalize per-tab response state loaded from local storage.
  // Input: candidate response payload.
  // Output: normalized response hash or null.
  function normalizeResponse(input) {
    if (!input || typeof input !== 'object') return null;
    var response = input.response && typeof input.response === 'object' ? input.response : {};
    var request = input.request && typeof input.request === 'object' ? input.request : {};
    return {
      ok: !!input.ok,
      error: input.error ? String(input.error) : '',
      request: {
        method: request.method ? String(request.method) : '',
        url: request.url ? String(request.url) : '',
        headers_text: request.headers_text ? String(request.headers_text) : '',
        body: request.body ? String(request.body) : '',
        timeout_s: request.timeout_s || 120,
        follow_redirects: request.follow_redirects !== false,
        insecure_tls: !!request.insecure_tls
      },
      response: {
        status: response.status || 0,
        reason: response.reason ? String(response.reason) : '',
        elapsed_ms: response.elapsed_ms || 0,
        content_type: response.content_type ? String(response.content_type) : '',
        headers: Array.isArray(response.headers) ? response.headers : [],
        body: response.body ? String(response.body) : '',
        body_mode: response.body_mode ? String(response.body_mode) : 'text',
        preview_media_type: response.preview_media_type ? String(response.preview_media_type) : '',
        preview_url: response.preview_url ? String(response.preview_url) : '',
        body_size_bytes: response.body_size_bytes || 0
      }
    };
  }

  // normalizeRequestData(input)
  // Purpose: sanitize request settings loaded from collections, tabs, or imports.
  // Input: candidate request hash.
  // Output: normalized request hash.
  function normalizeRequestData(input) {
    var request = input && typeof input === 'object' ? input : {};
    var data = defaultRequest(request.name || 'New Request');
    if (request.method) data.method = String(request.method).toUpperCase();
    if (request.url) data.url = String(request.url);
    if (request.headers_text) data.headers_text = String(request.headers_text);
    if (request.body) data.body = String(request.body);
    if (request.description) data.description = String(request.description);
    if (request.timeout_s) data.timeout_s = request.timeout_s;
    if (request.insecure_tls) data.insecure_tls = true;
    if (request.follow_redirects === false) data.follow_redirects = false;
    return data;
  }

  // normalizeNode(node)
  // Purpose: sanitize a collection tree node.
  // Input: candidate folder or request node.
  // Output: normalized node hash or null.
  function normalizeNode(node) {
    if (!node || typeof node !== 'object') return null;
    if (Array.isArray(node.item)) {
      return {
        id: node.id || uid('folder'),
        kind: 'folder',
        name: node.name || 'Folder',
        item: node.item.map(normalizeNode).filter(Boolean)
      };
    }
    var request = normalizeRequestData(node.request || node);
    return {
      id: node.id || uid('request'),
      kind: 'request',
      name: node.name || request.name || 'Request',
      request: request
    };
  }

  // normalizeCollection(collection)
  // Purpose: sanitize a Postman-style collection object for local use.
  // Input: candidate collection hash.
  // Output: normalized collection hash or null.
  function normalizeCollection(collection) {
    if (!collection || typeof collection !== 'object') return null;
    return {
      id: collection.id || uid('collection'),
      name: collection.name || (collection.info && collection.info.name) || 'Collection',
      description: collection.description || (collection.info && collection.info.description) || '',
      variable: (collection.variable || []).map(normalizeCollectionVariable).filter(Boolean),
      item: (collection.item || []).map(normalizeNode).filter(Boolean)
    };
  }

  // normalizeTab(tab)
  // Purpose: sanitize one workspace tab and its last response.
  // Input: candidate tab hash.
  // Output: normalized tab hash or null.
  function normalizeTab(tab) {
    if (!tab || typeof tab !== 'object') return null;
    return {
      id: tab.id || uid('tab'),
      title: tab.title || (tab.request && tab.request.name) || 'New Tab',
      collection_id: tab.collection_id || '',
      request_id: tab.request_id || '',
      request: normalizeRequestData(tab.request || {}),
      response: normalizeResponse(tab.response || null)
    };
  }

  // flattenRequests(items, out)
  // Purpose: flatten a nested collection tree into request nodes only.
  // Input: item array and output array.
  // Output: output array.
  function flattenRequests(items, out) {
    (items || []).forEach(function (item) {
      if (item.kind === 'folder') {
        flattenRequests(item.item || [], out);
        return;
      }
      out.push(item);
    });
    return out;
  }

  // findFirstRequest(collection)
  // Purpose: choose a default request node from a collection.
  // Input: normalized collection hash.
  // Output: request node hash.
  function findFirstRequest(collection) {
    var requests = flattenRequests(collection.item || [], []);
    return requests[0] || {
      id: uid('request'),
      kind: 'request',
      name: 'New Request',
      request: defaultRequest('New Request')
    };
  }

  // tabFromRequest(node, collectionId)
  // Purpose: open a request node inside a new workspace tab.
  // Input: request node and owning collection id.
  // Output: tab hash.
  function tabFromRequest(node, collectionId) {
    return {
      id: uid('tab'),
      title: node.name || (node.request && node.request.name) || 'Request',
      collection_id: collectionId || '',
      request_id: node.id || '',
      request: normalizeRequestData(node.request || node),
      response: null
    };
  }

  // normalizeState(input)
  // Purpose: sanitize the persisted workspace state.
  // Input: candidate state hash.
  // Output: normalized workspace state.
  function normalizeState(input) {
    var data = input && typeof input === 'object' ? input : {};
    var collections = Array.isArray(data.collections) ? data.collections.map(normalizeCollection).filter(Boolean) : [];
    var tabs = Array.isArray(data.tabs) ? data.tabs.map(normalizeTab).filter(Boolean) : [];
    if (!collections.length) collections = [scratchCollection()];
    if (!tabs.length) tabs = [tabFromRequest(findFirstRequest(collections[0]), collections[0].id)];
    return {
      collections: collections,
      tabs: tabs,
      active_tab_id: data.active_tab_id || tabs[0].id,
      active_shell_tab: data.active_shell_tab === 'collections' ? 'collections' : 'workspace',
      active_response_tab: data.active_response_tab === 'request' || data.active_response_tab === 'headers' ? data.active_response_tab : 'body',
      selected_collection_id: data.selected_collection_id || collections[0].id,
      selected_request_id: data.selected_request_id || (tabs[0].request_id || '')
    };
  }

  // loadState()
  // Purpose: load local workspace state from browser storage.
  // Input: none.
  // Output: normalized workspace state.
  function loadState() {
    try {
      var raw = window.localStorage.getItem(STORAGE_KEY);
      if (!raw) return normalizeState({});
      return normalizeState(JSON.parse(raw));
    } catch (error) {
      showBanner('Unable to read saved API dashboard state: ' + error.message, true);
      return normalizeState({});
    }
  }

  // saveState()
  // Purpose: persist the current workspace state to local storage.
  // Input: none.
  // Output: none.
  function saveState() {
    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
  }

  // setShellTab(name)
  // Purpose: activate one top-level shell tab and persist the preference.
  // Input: tab name string.
  // Output: none.
  function setShellTab(name) {
    state.active_shell_tab = name === 'collections' ? 'collections' : 'workspace';
    saveState();
  }

  // setResponseTab(name)
  // Purpose: activate one response panel tab and persist the preference.
  // Input: tab name string.
  // Output: none.
  function setResponseTab(name) {
    if (name !== 'request' && name !== 'headers') name = 'body';
    state.active_response_tab = name;
    saveState();
  }

  // findCollection(collectionId)
  // Purpose: resolve a collection by id from the current state.
  // Input: collection id string.
  // Output: collection hash or null.
  function findCollection(collectionId) {
    return (state.collections || []).find(function (collection) {
      return collection.id === collectionId;
    }) || null;
  }

  // findCollectionByName(name)
  // Purpose: resolve a collection by name from the current state.
  // Input: collection name string.
  // Output: collection hash or null.
  function findCollectionByName(name) {
    return (state.collections || []).find(function (collection) {
      return collection.name === name;
    }) || null;
  }

  // collectionNameExists(name, exceptCollectionId)
  // Purpose: detect collection name conflicts before saving or renaming.
  // Input: collection name string and optional collection id to exclude.
  // Output: boolean.
  function collectionNameExists(name, exceptCollectionId) {
    var trimmed = String(name || '').trim();
    return (state.collections || []).some(function (collection) {
      if (exceptCollectionId && collection.id === exceptCollectionId) return false;
      return collection.name === trimmed;
    });
  }

  // findNode(items, nodeId)
  // Purpose: resolve a request or folder node by id from a nested tree.
  // Input: items array and node id string.
  // Output: node hash or null.
  function findNode(items, nodeId) {
    for (var i = 0; i < (items || []).length; i++) {
      var item = items[i];
      if (item.id === nodeId) return item;
      if (item.kind === 'folder') {
        var found = findNode(item.item || [], nodeId);
        if (found) return found;
      }
    }
    return null;
  }

  // findRequestAcrossCollections(nodeId)
  // Purpose: locate a request node anywhere in the workspace by id.
  // Input: request node id string.
  // Output: object with collection and node keys, or null.
  function findRequestAcrossCollections(nodeId) {
    var i;
    for (i = 0; i < (state.collections || []).length; i++) {
      var collection = state.collections[i];
      var node = findNode(collection.item || [], nodeId);
      if (node && node.kind === 'request') {
        return {
          collection: collection,
          node: node
        };
      }
    }
    return null;
  }

  // findTab(tabId)
  // Purpose: resolve a tab by id from the current state.
  // Input: tab id string.
  // Output: tab hash or null.
  function findTab(tabId) {
    return (state.tabs || []).find(function (tab) {
      return tab.id === tabId;
    }) || null;
  }

  // findTabForRequest(collectionId, requestId)
  // Purpose: locate an already-open tab for a saved request.
  // Input: collection id and request id strings.
  // Output: tab hash or null.
  function findTabForRequest(collectionId, requestId) {
    return (state.tabs || []).find(function (tab) {
      return tab.collection_id === collectionId && tab.request_id === requestId;
    }) || null;
  }

  // adoptNodeIds(previousItems, incomingItems)
  // Purpose: preserve browser-local node ids across bootstrap refreshes when names and ordering still match.
  // Input: previous node array and incoming node array.
  // Output: none.
  function adoptNodeIds(previousItems, incomingItems) {
    var oldItems = previousItems || [];
    (incomingItems || []).forEach(function (item, index) {
      var previous = oldItems[index];
      if (!previous || previous.kind !== item.kind || previous.name !== item.name) return;
      item.id = previous.id;
      if (item.kind === 'folder') adoptNodeIds(previous.item || [], item.item || []);
    });
  }

  // adoptCollectionIds(previousCollection, incomingCollection)
  // Purpose: preserve browser-local collection and request ids when the same collection reloads from disk.
  // Input: previous collection hash and incoming collection hash.
  // Output: none.
  function adoptCollectionIds(previousCollection, incomingCollection) {
    if (!previousCollection || !incomingCollection) return;
    incomingCollection.id = previousCollection.id;
    adoptNodeIds(previousCollection.item || [], incomingCollection.item || []);
  }

  // activeTab()
  // Purpose: resolve the currently active tab.
  // Input: none.
  // Output: tab hash or null.
  function activeTab() {
    return (state.tabs || []).find(function (tab) {
      return tab.id === state.active_tab_id;
    }) || null;
  }

  // ensureActiveTab()
  // Purpose: guarantee the state points at an existing tab.
  // Input: none.
  // Output: none.
  function ensureActiveTab() {
    if (!activeTab() && state.tabs.length) state.active_tab_id = state.tabs[0].id;
  }

  // formatVariables(variable)
  // Purpose: convert collection variables to editable textarea text.
  // Input: variable array.
  // Output: string.
  function formatVariables(variable) {
    return (variable || []).map(function (entry) {
      return entry.key + '=' + (entry.value == null ? '' : entry.value);
    }).join('\n');
  }

  // parseVariablesText(text)
  // Purpose: parse editable textarea text into collection variables.
  // Input: textarea string.
  // Output: normalized variable array.
  function parseVariablesText(text) {
    return String(text || '').split(/\n/).map(function (line) {
      var trimmed = line.trim();
      if (!trimmed) return null;
      var pos = trimmed.indexOf('=');
      if (pos < 1) return null;
      return normalizeCollectionVariable({
        key: trimmed.slice(0, pos),
        value: trimmed.slice(pos + 1)
      });
    }).filter(Boolean);
  }

  // variableMap(collection)
  // Purpose: build a lookup hash for collection variables.
  // Input: collection hash.
  // Output: object map.
  function variableMap(collection) {
    var hash = {};
    (collection && collection.variable || []).forEach(function (entry) {
      hash[entry.key] = entry.value == null ? '' : String(entry.value);
    });
    return hash;
  }

  // applyVariables(collection, text)
  // Purpose: expand {{token}} placeholders from the selected collection variables.
  // Input: collection hash and source string.
  // Output: expanded string.
  function applyVariables(collection, text) {
    var value = String(text == null ? '' : text);
    var hash = variableMap(collection);
    return value.replace(/\{\{([^{}]+)\}\}/g, function (match, token) {
      var key = String(token || '').trim();
      return Object.prototype.hasOwnProperty.call(hash, key) ? hash[key] : match;
    });
  }

  // requestTokenNames(request)
  // Purpose: extract unique {{token}} placeholders used by one request template.
  // Input: normalized request hash.
  // Output: sorted array of token-name strings.
  function requestTokenNames(request) {
    var joined = [
      request && request.url || '',
      request && request.headers_text || '',
      request && request.body || ''
    ].join('\n');
    var found = {};
    joined.replace(/\{\{([^{}]+)\}\}/g, function (_, token) {
      var key = String(token || '').trim();
      if (key) found[key] = 1;
      return _;
    });
    return Object.keys(found).sort();
  }

  // upsertCollectionVariable(collection, key, value)
  // Purpose: create or update one collection variable entry in place.
  // Input: collection hash, variable key string, and variable value string.
  // Output: none.
  function upsertCollectionVariable(collection, key, value) {
    if (!collection) return;
    var entry = (collection.variable || []).find(function (item) {
      return item.key === key;
    });
    if (entry) {
      entry.value = value;
      return;
    }
    collection.variable = collection.variable || [];
    collection.variable.push({
      key: key,
      value: value
    });
  }

  // ensureRequestTokens(collection, request)
  // Purpose: make sure the active collection has variable entries for every token used by the active request.
  // Input: collection hash and normalized request hash.
  // Output: sorted token-name array.
  function ensureRequestTokens(collection, request) {
    var names = requestTokenNames(request);
    names.forEach(function (name) {
      upsertCollectionVariable(collection, name, variableMap(collection)[name] || '');
    });
    return names;
  }

  // setResolvedFieldValue(elementId, rawValue, resolvedValue)
  // Purpose: populate one request editor field with resolved text while preserving the raw template text in a data attribute.
  // Input: DOM element id string, raw template string, and resolved display string.
  // Output: none.
  function setResolvedFieldValue(elementId, rawValue, resolvedValue) {
    var element = document.getElementById(elementId);
    if (!element) return;
    element.dataset.apiRawValue = rawValue == null ? '' : String(rawValue);
    element.value = resolvedValue == null ? '' : String(resolvedValue);
  }

  // readResolvedFieldValue(elementId, collection)
  // Purpose: recover the raw template value for one editor field unless the user has changed the resolved display text.
  // Input: DOM element id string and collection hash for token expansion.
  // Output: raw template or current edited string.
  function readResolvedFieldValue(elementId, collection) {
    var element = document.getElementById(elementId);
    if (!element) return '';
    var raw = element.dataset.apiRawValue || '';
    var resolved = applyVariables(collection, raw);
    return element.value === resolved ? raw : element.value;
  }

  // renderResolvedRequestFields(collection, tab)
  // Purpose: repaint the request URL, headers, and body editors with collection-token expansion while preserving the raw templates.
  // Input: collection hash and active tab hash.
  // Output: none.
  function renderResolvedRequestFields(collection, tab) {
    setResolvedFieldValue('api-request-url', tab.request.url || '', applyVariables(collection, tab.request.url || ''));
    setResolvedFieldValue('api-request-headers', tab.request.headers_text || '', applyVariables(collection, tab.request.headers_text || ''));
    setResolvedFieldValue('api-request-body', tab.request.body || '', applyVariables(collection, tab.request.body || ''));
  }

  // renderTokenFields(collection, tab)
  // Purpose: render the request-specific token input form above the editor and keep the resolved request fields in sync.
  // Input: collection hash and active tab hash.
  // Output: none.
  function renderTokenFields(collection, tab) {
    var shell = document.getElementById('api-token-shell');
    var fields = document.getElementById('api-token-fields');
    fields.innerHTML = '';
    if (!collection || !tab) {
      shell.hidden = true;
      return;
    }
    var names = ensureRequestTokens(collection, tab.request);
    if (!names.length) {
      shell.hidden = true;
      return;
    }
    shell.hidden = false;
    names.forEach(function (name) {
      var field = document.createElement('div');
      field.className = 'api-token-field';
      var label = document.createElement('label');
      label.textContent = name;
      label.setAttribute('for', 'api-token-input-' + name.replace(/[^A-Za-z0-9_-]+/g, '-'));
      var input = document.createElement('input');
      input.id = 'api-token-input-' + name.replace(/[^A-Za-z0-9_-]+/g, '-');
      input.type = 'text';
      input.value = variableMap(collection)[name] || '';
      input.setAttribute('data-api-token-input', name);
      input.placeholder = '{{' + name + '}}';
      input.addEventListener('input', function () {
        upsertCollectionVariable(collection, name, input.value);
        document.getElementById('api-request-variables').value = formatVariables(collection.variable || []);
        renderResolvedRequestFields(collection, activeTab());
        saveState();
      });
      input.addEventListener('change', function () {
        upsertCollectionVariable(collection, name, input.value);
        document.getElementById('api-request-variables').value = formatVariables(collection.variable || []);
        persistWorkspace(true);
      });
      field.appendChild(label);
      field.appendChild(input);
      fields.appendChild(field);
    });
  }

  // responseChip(text, isError)
  // Purpose: build a response metadata badge.
  // Input: label text and boolean error flag.
  // Output: span element.
  function responseChip(text, isError) {
    var span = document.createElement('span');
    span.className = 'api-chip' + (isError ? ' is-error' : '');
    span.textContent = text;
    return span;
  }

  // currentRoute()
  // Purpose: capture the current browser-restorable workspace location.
  // Input: none.
  // Output: route hash with collection, request, and tab ids.
  function currentRoute() {
    var tab = activeTab();
    return {
      collection: state.selected_collection_id || (tab && tab.collection_id) || '',
      request: state.selected_request_id || (tab && tab.request_id) || '',
      tab: state.active_tab_id || ''
    };
  }

  // routeQuery(route)
  // Purpose: serialize a workspace location into URL query parameters.
  // Input: route hash.
  // Output: query string without leading question mark.
  function routeQuery(route) {
    var params = new window.URLSearchParams();
    if (route.collection) params.set('collection', route.collection);
    if (route.request) params.set('request', route.request);
    if (route.tab) params.set('tab', route.tab);
    return params.toString();
  }

  // readRoute()
  // Purpose: parse the current browser URL into a workspace location.
  // Input: none.
  // Output: route hash.
  function readRoute() {
    var params = new window.URLSearchParams(window.location.search || '');
    return {
      collection: params.get('collection') || '',
      request: params.get('request') || '',
      tab: params.get('tab') || ''
    };
  }

  // syncRoute(replaceRoute)
  // Purpose: keep browser history aligned with the active workspace selection.
  // Input: boolean replace flag.
  // Output: none.
  function syncRoute(replaceRoute) {
    if (!window.history || !window.URLSearchParams) return;
    var query = routeQuery(currentRoute());
    var target = window.location.pathname + (query ? '?' + query : '');
    var current = window.location.pathname + window.location.search;
    if (target === current) return;
    if (replaceRoute) {
      window.history.replaceState(currentRoute(), '', target);
      return;
    }
    window.history.pushState(currentRoute(), '', target);
  }

  // applyRoute(route, options)
  // Purpose: restore workspace selection from a direct URL or browser navigation event.
  // Input: route hash and options hash.
  // Output: none.
  function applyRoute(route, options) {
    options = options || {};
    if (route.collection && findCollection(route.collection)) {
      state.selected_collection_id = route.collection;
    }
    if (route.tab) {
      var existingTab = findTab(route.tab);
      if (existingTab) {
        state.active_tab_id = existingTab.id;
        state.selected_collection_id = existingTab.collection_id || state.selected_collection_id;
        state.selected_request_id = existingTab.request_id || route.request || '';
        return;
      }
    }
    if (route.request) {
      var found = findRequestAcrossCollections(route.request);
      if (found) {
        state.selected_collection_id = found.collection.id;
        state.selected_request_id = found.node.id;
        var existingRequestTab = findTabForRequest(found.collection.id, found.node.id);
        if (existingRequestTab) {
          state.active_tab_id = existingRequestTab.id;
        } else if (options.allow_open !== false) {
          var opened = tabFromRequest(found.node, found.collection.id);
          state.tabs.push(opened);
          state.active_tab_id = opened.id;
        }
        return;
      }
    }
    if (!state.selected_request_id && route.collection) {
      state.selected_request_id = '';
    }
  }

  // persistWorkspace(replaceRoute)
  // Purpose: persist local state and synchronize the URL for browser navigation.
  // Input: boolean replace flag.
  // Output: none.
  function persistWorkspace(replaceRoute) {
    saveState();
    syncRoute(!!replaceRoute);
  }

  // renderPreview(payload)
  // Purpose: render browser previews for previewable media responses.
  // Input: normalized response payload.
  // Output: none.
  function renderPreview(payload) {
    var preview = document.getElementById('api-response-preview');
    preview.className = 'api-response-preview';
    preview.innerHTML = '';
    if (!payload || !payload.ok || payload.response.body_mode !== 'preview' || !payload.response.preview_url) {
      return;
    }
    var mediaType = payload.response.preview_media_type || '';
    var node;
    if (/^image\/(?!tiff?)/i.test(mediaType)) {
      node = document.createElement('img');
      node.src = payload.response.preview_url;
      node.alt = 'API response preview';
    } else if (/pdf/i.test(mediaType)) {
      node = document.createElement('iframe');
      node.src = payload.response.preview_url;
      node.title = 'PDF response preview';
    } else {
      node = document.createElement('object');
      node.data = payload.response.preview_url;
      node.type = mediaType || 'application/octet-stream';
    }
    preview.appendChild(node);
    preview.className += ' is-visible';
  }

  // renderResponse(payload)
  // Purpose: render request and response details for the active tab.
  // Input: normalized response payload or null.
  // Output: none.
  function renderResponse(payload) {
    var meta = document.getElementById('api-response-meta');
    var preview = document.getElementById('api-response-preview');
    var body = document.getElementById('api-response-body');
    var headers = document.getElementById('api-response-headers');
    var request = document.getElementById('api-request-details');
    var requestPayload = payload && payload.request || {};
    var responsePayload = payload && payload.response || {};
    meta.innerHTML = '';
    preview.className = 'api-response-preview';
    preview.innerHTML = '';
    if (!payload) {
      body.textContent = 'Ready.';
      headers.textContent = 'Ready.';
      request.textContent = 'Ready.';
      return;
    }
    request.textContent = [
      'Method: ' + (requestPayload.method || ''),
      'URL: ' + (requestPayload.url || ''),
      'Timeout: ' + (requestPayload.timeout_s || 120) + 's',
      'Follow Redirects: ' + (requestPayload.follow_redirects === false ? 'No' : 'Yes'),
      'Allow Insecure TLS: ' + (requestPayload.insecure_tls ? 'Yes' : 'No'),
      '',
      'Headers:',
      requestPayload.headers_text || '(none)',
      '',
      'Body:',
      requestPayload.body || '(empty)'
    ].join('\n');
    if (!payload.ok) {
      meta.appendChild(responseChip('Request failed', true));
      body.textContent = payload.error || 'Request failed.';
      headers.textContent = '';
      return;
    }
    meta.appendChild(responseChip('HTTP ' + responsePayload.status));
    meta.appendChild(responseChip((responsePayload.elapsed_ms || 0) + 'ms'));
    meta.appendChild(responseChip(responsePayload.content_type || 'unknown content type'));
    meta.appendChild(responseChip((responsePayload.body_size_bytes || 0) + ' bytes'));
    if (responsePayload.body_mode === 'preview') {
      body.textContent = 'Preview rendered below.';
      renderPreview(payload);
    } else {
      body.textContent = responsePayload.body || '';
    }
    headers.textContent = (responsePayload.headers || []).map(function (entry) {
      return entry.key + ': ' + entry.value;
    }).join('\n');
  }

  // currentLocationText()
  // Purpose: build a human-readable breadcrumb for the left panel.
  // Input: none.
  // Output: string.
  function currentLocationText() {
    var collection = findCollection(state.selected_collection_id);
    var tab = activeTab();
    var parts = ['Current Location:'];
    if (collection) parts.push(collection.name);
    if (state.selected_request_id) {
      var found = findRequestAcrossCollections(state.selected_request_id);
      if (found) parts.push(found.node.name);
    } else if (tab && tab.title) {
      parts.push(tab.title);
    } else {
      parts.push('Workspace root');
    }
    return parts.join(' / ');
  }

  // renderNodeList(collectionId, items)
  // Purpose: render one nested collection tree branch.
  // Input: collection id and item array.
  // Output: ul element.
  function renderNodeList(collectionId, items) {
    var list = document.createElement('ul');
    list.className = 'api-node-list';
    if (!items.length) {
      var empty = document.createElement('li');
      empty.className = 'api-empty';
      empty.textContent = 'No requests saved in this collection yet.';
      list.appendChild(empty);
      return list;
    }
    items.forEach(function (item) {
      var li = document.createElement('li');
      var button = document.createElement('button');
      button.type = 'button';
      button.className = 'api-node-button' + (item.kind === 'folder' ? ' api-node-folder' : '');
      if (item.kind === 'request' && item.id === state.selected_request_id) button.className += ' is-active';
      button.textContent = item.kind === 'folder' ? 'Folder: ' + item.name : item.name;
      button.addEventListener('click', function () {
        state.selected_collection_id = collectionId;
        if (item.kind !== 'folder') {
          state.selected_request_id = item.id;
          openNodeInTab(collectionId, item.id);
          return;
        }
        persistWorkspace(false);
        renderAll();
      });
      li.appendChild(button);
      if (item.kind === 'folder') li.appendChild(renderNodeList(collectionId, item.item || []));
      list.appendChild(li);
    });
    return list;
  }

  // renderCollections()
  // Purpose: render the collection sidebar and location breadcrumb.
  // Input: none.
  // Output: none.
  function renderCollections() {
    var tree = document.getElementById('api-collection-tree');
    tree.innerHTML = '';
    document.getElementById('api-collection-location').textContent = currentLocationText();
    var activeCollection = findCollection(state.selected_collection_id) || state.collections[0];
    if (!activeCollection) return;

    var tabList = document.createElement('div');
    tabList.className = 'api-collection-tabs';
    tabList.setAttribute('role', 'tablist');
    tabList.setAttribute('aria-label', 'Stored Collections');

    state.collections.forEach(function (collection) {
      var button = document.createElement('button');
      button.type = 'button';
      button.className = 'api-collection-tab';
      button.textContent = collection.name;
      button.setAttribute('role', 'tab');
      button.setAttribute('aria-selected', collection.id === activeCollection.id ? 'true' : 'false');
      button.setAttribute('aria-controls', 'api-collection-panel');
      button.addEventListener('click', function () {
        state.selected_collection_id = collection.id;
        state.selected_request_id = '';
        persistWorkspace(false);
        renderAll();
      });
      tabList.appendChild(button);
    });
    tree.appendChild(tabList);

    var card = document.createElement('section');
    card.className = 'api-collection-card is-active';
    card.id = 'api-collection-panel';
    card.setAttribute('role', 'tabpanel');
    card.setAttribute('data-api-collection-panel', activeCollection.name);
    var title = document.createElement('h3');
    title.textContent = activeCollection.name;
    card.appendChild(title);
    if (activeCollection.description) {
      var note = document.createElement('p');
      note.textContent = activeCollection.description;
      note.style.color = '#6a767b';
      note.style.margin = '0 0 10px';
      card.appendChild(note);
    }
    card.appendChild(renderNodeList(activeCollection.id, activeCollection.item || []));
    tree.appendChild(card);
  }

  // renderShellTabs()
  // Purpose: toggle the top-level Collections and Workspace tab panels.
  // Input: none.
  // Output: none.
  function renderShellTabs() {
    ['collections', 'workspace'].forEach(function (name) {
      var button = document.getElementById('api-shell-tab-' + name);
      var panel = document.getElementById('api-shell-panel-' + name);
      var selected = state.active_shell_tab === name;
      button.setAttribute('aria-selected', selected ? 'true' : 'false');
      panel.hidden = !selected;
    });
  }

  // renderTabs()
  // Purpose: render the open request tabs and keep them browser-addressable.
  // Input: none.
  // Output: none.
  function renderTabs() {
    var strip = document.getElementById('api-tab-strip');
    strip.innerHTML = '';
    state.tabs.forEach(function (tab) {
      var chip = document.createElement('div');
      chip.className = 'api-tab' + (tab.id === state.active_tab_id ? ' is-active' : '');
      chip.addEventListener('click', function () {
        state.active_tab_id = tab.id;
        state.selected_collection_id = tab.collection_id || state.selected_collection_id;
        state.selected_request_id = tab.request_id || '';
        persistWorkspace(false);
        renderAll();
      });
      var label = document.createElement('span');
      label.textContent = tab.title || 'Untitled Tab';
      chip.appendChild(label);
      var close = document.createElement('button');
      close.type = 'button';
      close.className = 'api-tab-close';
      close.textContent = 'x';
      close.addEventListener('click', function (event) {
        event.stopPropagation();
        closeTab(tab.id);
      });
      chip.appendChild(close);
      strip.appendChild(chip);
    });
  }

  // renderEditor()
  // Purpose: populate the editor form and response area from the active tab.
  // Input: none.
  // Output: none.
  function renderEditor() {
    ensureActiveTab();
    var tab = activeTab();
    if (!tab) {
      document.getElementById('api-token-shell').hidden = true;
      renderResponse(null);
      return;
    }
    var collection = findCollection(tab.collection_id || state.selected_collection_id) || findCollection(state.selected_collection_id);
    document.getElementById('api-request-name').value = tab.request.name || tab.title || '';
    document.getElementById('api-request-method').value = tab.request.method || 'GET';
    renderResolvedRequestFields(collection, tab);
    document.getElementById('api-request-description').value = tab.request.description || '';
    document.getElementById('api-timeout').value = tab.request.timeout_s || 120;
    document.getElementById('api-follow-redirects').checked = tab.request.follow_redirects !== false;
    document.getElementById('api-insecure-tls').checked = !!tab.request.insecure_tls;
    document.getElementById('api-request-variables').value = formatVariables(collection ? collection.variable : []);
    document.getElementById('api-collection-name').value = collection ? collection.name : '';
    renderTokenFields(collection, tab);
    renderResponse(tab.response || null);
  }

  // renderResponseTabs()
  // Purpose: toggle the inner request/response tab panels.
  // Input: none.
  // Output: none.
  function renderResponseTabs() {
    ['request', 'body', 'headers'].forEach(function (name) {
      var button = document.getElementById('api-response-tab-' + name);
      var panel = document.getElementById('api-response-panel-' + name);
      var selected = state.active_response_tab === name;
      button.setAttribute('aria-selected', selected ? 'true' : 'false');
      panel.hidden = !selected;
    });
  }

  // syncActiveTabFromForm()
  // Purpose: copy the current form values back into the active tab state.
  // Input: none.
  // Output: none.
  function syncActiveTabFromForm() {
    var tab = activeTab();
    if (!tab) return;
    var collection = findCollection(state.selected_collection_id) || findCollection(tab.collection_id) || null;
    tab.request.name = document.getElementById('api-request-name').value.trim() || 'New Request';
    tab.request.method = document.getElementById('api-request-method').value;
    tab.request.url = readResolvedFieldValue('api-request-url', collection).trim();
    tab.request.headers_text = readResolvedFieldValue('api-request-headers', collection);
    tab.request.body = readResolvedFieldValue('api-request-body', collection);
    tab.request.description = document.getElementById('api-request-description').value;
    tab.request.timeout_s = parseInt(document.getElementById('api-timeout').value || '120', 10) || 120;
    tab.request.follow_redirects = document.getElementById('api-follow-redirects').checked;
    tab.request.insecure_tls = document.getElementById('api-insecure-tls').checked;
    tab.title = tab.request.name;
    if (collection) {
      collection.variable = parseVariablesText(document.getElementById('api-request-variables').value);
      ensureRequestTokens(collection, tab.request);
      if (!tab.collection_id) tab.collection_id = collection.id;
    }
    saveState();
    renderTabs();
  }

  // closeTab(tabId)
  // Purpose: close one request tab and keep selection valid.
  // Input: tab id string.
  // Output: none.
  function closeTab(tabId) {
    state.tabs = state.tabs.filter(function (tab) { return tab.id !== tabId; });
    if (!state.tabs.length) state.tabs.push(tabFromRequest(findFirstRequest(state.collections[0]), state.collections[0].id));
    ensureActiveTab();
    var tab = activeTab();
    state.selected_collection_id = (tab && tab.collection_id) || state.selected_collection_id;
    state.selected_request_id = (tab && tab.request_id) || '';
    persistWorkspace(true);
    renderAll();
  }

  // openNodeInTab(collectionId, nodeId)
  // Purpose: open or focus a saved request from the collection tree.
  // Input: collection id and request id strings.
  // Output: none.
  function openNodeInTab(collectionId, nodeId) {
    var collection = findCollection(collectionId);
    if (!collection) return;
    var node = findNode(collection.item || [], nodeId);
    if (!node || node.kind !== 'request') return;
    setShellTab('workspace');
    state.selected_collection_id = collectionId;
    state.selected_request_id = node.id;
    var existing = findTabForRequest(collectionId, nodeId);
    if (existing) {
      state.active_tab_id = existing.id;
    } else {
      state.tabs.push(tabFromRequest(node, collectionId));
      state.active_tab_id = state.tabs[state.tabs.length - 1].id;
    }
    persistWorkspace(false);
    renderAll();
  }

  // createBlankTab()
  // Purpose: open a new unsaved request tab.
  // Input: none.
  // Output: none.
  function createBlankTab() {
    var collection = findCollection(state.selected_collection_id) || state.collections[0];
    setShellTab('workspace');
    state.tabs.push({
      id: uid('tab'),
      title: 'New Tab',
      collection_id: collection ? collection.id : '',
      request_id: '',
      request: defaultRequest('New Request'),
      response: null
    });
    state.active_tab_id = state.tabs[state.tabs.length - 1].id;
    state.selected_request_id = '';
    persistWorkspace(false);
    renderAll();
  }

  // duplicateActiveTab()
  // Purpose: clone the active request into a new tab.
  // Input: none.
  // Output: none.
  function duplicateActiveTab() {
    var tab = activeTab();
    if (!tab) return;
    setShellTab('workspace');
    var copy = clone(tab);
    copy.id = uid('tab');
    copy.title = (copy.title || 'Request') + ' Copy';
    copy.request.name = copy.title;
    copy.request_id = '';
    state.tabs.push(copy);
    state.active_tab_id = copy.id;
    state.selected_request_id = '';
    persistWorkspace(false);
    renderAll();
  }

  // saveRequestToCollection()
  // Purpose: save or update the active request inside the selected collection.
  // Input: none.
  // Output: none.
  function saveRequestToCollection() {
    syncActiveTabFromForm();
    setShellTab('workspace');
    var tab = activeTab();
    var collection = findCollection(state.selected_collection_id) || findCollection(tab.collection_id) || state.collections[0];
    if (!collection) return;
    state.selected_collection_id = collection.id;
    tab.collection_id = collection.id;
    if (tab.request_id) {
      var existing = findNode(collection.item || [], tab.request_id);
      if (existing && existing.kind === 'request') {
        existing.name = tab.request.name;
        existing.request = clone(tab.request);
        state.selected_request_id = existing.id;
        persistWorkspace(true);
        renderAll();
        persistCollectionToServer(collection, {
          successMessage: 'Updated request "' + existing.name + '" in ' + collection.name + '.',
          failureMessage: 'Unable to save the updated request to ' + collection.name + '.'
        });
        return;
      }
    }
    var node = {
      id: uid('request'),
      kind: 'request',
      name: tab.request.name,
      request: clone(tab.request)
    };
    collection.item.push(node);
    tab.request_id = node.id;
    state.selected_request_id = node.id;
    persistWorkspace(true);
    renderAll();
    persistCollectionToServer(collection, {
      successMessage: 'Saved request "' + node.name + '" to ' + collection.name + '.',
      failureMessage: 'Unable to save the request to ' + collection.name + '.'
    });
  }

  // createCollection()
  // Purpose: add a new empty collection to the workspace.
  // Input: none.
  // Output: none.
  function createCollection() {
    var name = window.prompt('Collection name', 'New Collection');
    if (!name) return;
    name = String(name || '').trim();
    if (!name) return;
    if (collectionNameExists(name)) {
      showBanner('A collection named "' + name + '" already exists.', true);
      return;
    }
    var collection = {
      id: uid('collection'),
      name: name,
      description: '',
      variable: [],
      item: []
    };
    setShellTab('collections');
    state.collections.push(collection);
    state.selected_collection_id = state.collections[state.collections.length - 1].id;
    state.selected_request_id = '';
    persistWorkspace(false);
    renderAll();
    persistCollectionToServer(collection, {
      successMessage: 'Created collection "' + name + '".',
      failureMessage: 'Unable to create collection "' + name + '".'
    });
  }

  // renameSelectedCollection()
  // Purpose: rename the active collection.
  // Input: none.
  // Output: none.
  function renameSelectedCollection() {
    var collection = findCollection(state.selected_collection_id) || state.collections[0];
    if (!collection) return;
    var name = window.prompt('Rename collection', collection.name);
    if (!name) return;
    name = String(name || '').trim();
    if (!name) return;
    if (collectionNameExists(name, collection.id)) {
      showBanner('A collection named "' + name + '" already exists.', true);
      return;
    }
    var originalName = collection.name;
    setShellTab('collections');
    collection.name = name;
    persistWorkspace(true);
    renderAll();
    persistCollectionToServer(collection, {
      original_name: originalName,
      successMessage: 'Renamed collection to "' + name + '".',
      failureMessage: 'Unable to rename collection "' + originalName + '".'
    });
  }

  // deleteSelectedCollection()
  // Purpose: remove the active collection and any tabs that depend on it.
  // Input: none.
  // Output: none.
  function deleteSelectedCollection() {
    var collection = findCollection(state.selected_collection_id) || state.collections[0];
    if (!collection) return;
    if (!window.confirm('Delete collection "' + collection.name + '"?')) return;
    var deletedName = collection.name;
    setShellTab('collections');
    state.collections = state.collections.filter(function (item) {
      return item.id !== collection.id;
    });
    state.tabs = state.tabs.filter(function (tab) {
      return tab.collection_id !== collection.id;
    });
    if (!state.collections.length) state.collections = [scratchCollection()];
    if (!state.tabs.length) state.tabs = [tabFromRequest(findFirstRequest(state.collections[0]), state.collections[0].id)];
    state.selected_collection_id = state.collections[0].id;
    state.selected_request_id = '';
    ensureActiveTab();
    persistWorkspace(false);
    renderAll();
    deleteCollectionFromServer(deletedName, {
      successMessage: 'Deleted collection "' + deletedName + '".',
      failureMessage: 'Unable to delete collection "' + deletedName + '".'
    });
  }

  // removeNode(items, nodeId)
  // Purpose: delete one request node from a nested collection tree.
  // Input: item array and node id string.
  // Output: boolean success flag.
  function removeNode(items, nodeId) {
    var i;
    for (i = 0; i < (items || []).length; i++) {
      var item = items[i];
      if (item.id === nodeId) {
        items.splice(i, 1);
        return true;
      }
      if (item.kind === 'folder' && removeNode(item.item || [], nodeId)) return true;
    }
    return false;
  }

  // deleteSavedRequest()
  // Purpose: remove the active saved request from its collection tree.
  // Input: none.
  // Output: none.
  function deleteSavedRequest() {
    var tab = activeTab();
    if (!tab || !tab.request_id) {
      showBanner('The active tab is not linked to a saved request.', true);
      return;
    }
    var collection = findCollection(tab.collection_id || state.selected_collection_id);
    if (!collection) return;
    if (!window.confirm('Delete saved request "' + (tab.request.name || tab.title) + '"?')) return;
    if (!removeNode(collection.item || [], tab.request_id)) {
      showBanner('Unable to delete the selected request from the collection tree.', true);
      return;
    }
    tab.request_id = '';
    state.selected_request_id = '';
    persistWorkspace(true);
    renderAll();
    persistCollectionToServer(collection, {
      successMessage: 'Deleted the saved request from ' + collection.name + '.',
      failureMessage: 'Unable to update ' + collection.name + ' after deleting the request.'
    });
  }

  // importRequest(request)
  // Purpose: convert a Postman request object into the local request model.
  // Input: Postman request hash.
  // Output: local request hash.
  function importRequest(request) {
    var url = '';
    if (request && request.url) {
      url = typeof request.url === 'string' ? request.url : (request.url.raw || '');
    }
    return {
      method: request && request.method ? String(request.method).toUpperCase() : 'GET',
      url: url,
      headers_text: (request && Array.isArray(request.header) ? request.header : []).map(function (header) {
        return (header.key || '') + ': ' + (header.value || '');
      }).join('\n'),
      body: request && request.body && typeof request.body.raw === 'string' ? request.body.raw : '',
      description: request && request.description ? String(request.description) : '',
      timeout_s: 120,
      insecure_tls: false,
      follow_redirects: true,
      name: request && request.name ? request.name : 'Request'
    };
  }

  // importPostmanNode(node)
  // Purpose: convert one imported Postman tree node into local state.
  // Input: Postman node hash.
  // Output: local node hash or null.
  function importPostmanNode(node) {
    if (!node || typeof node !== 'object') return null;
    if (Array.isArray(node.item)) {
      return {
        id: uid('folder'),
        kind: 'folder',
        name: node.name || 'Folder',
        item: node.item.map(importPostmanNode).filter(Boolean)
      };
    }
    var request = importRequest(node.request || node);
    return {
      id: uid('request'),
      kind: 'request',
      name: node.name || request.name || 'Request',
      request: request
    };
  }

  // importPostmanCollection(payload)
  // Purpose: validate and normalize an imported Postman collection payload.
  // Input: parsed JSON payload.
  // Output: local collection hash.
  function importPostmanCollection(payload) {
    var source = payload && payload.collection ? payload.collection : payload;
    if (!source || typeof source !== 'object') throw new Error('The imported JSON must contain a Postman collection object.');
    if (!source.info || !source.info.name) throw new Error('The imported collection is missing info.name.');
    return normalizeCollection({
      name: source.info.name,
      description: source.info.description || '',
      variable: source.variable || [],
      item: (source.item || []).map(importPostmanNode).filter(Boolean)
    });
  }

  // exportPostmanNode(node)
  // Purpose: convert one local node back into Postman-compatible JSON.
  // Input: local node hash.
  // Output: Postman node hash.
  function exportPostmanNode(node) {
    if (node.kind === 'folder') {
      return {
        name: node.name,
        item: (node.item || []).map(exportPostmanNode)
      };
    }
    return {
      name: node.name,
      request: {
        method: node.request.method || 'GET',
        header: String(node.request.headers_text || '').split(/\n/).map(function (line) {
          var trimmed = line.trim();
          if (!trimmed) return null;
          var pos = trimmed.indexOf(':');
          if (pos < 1) return null;
          return {
            key: trimmed.slice(0, pos).trim(),
            value: trimmed.slice(pos + 1).trim()
          };
        }).filter(Boolean),
        body: node.request.body ? { mode: 'raw', raw: node.request.body } : undefined,
        url: { raw: node.request.url || '' },
        description: node.request.description || ''
      }
    };
  }

  // exportPostmanCollection(collection)
  // Purpose: convert one local collection back into Postman v2.1 JSON.
  // Input: local collection hash.
  // Output: Postman collection hash.
  function exportPostmanCollection(collection) {
    return {
      info: {
        name: collection.name,
        description: collection.description || '',
        schema: POSTMAN_SCHEMA
      },
      variable: clone(collection.variable || []),
      item: (collection.item || []).map(exportPostmanNode)
    };
  }

  // importCollectionFile(file, inputElement)
  // Purpose: read and import a Postman collection file from disk.
  // Input: browser File object and optional input element for lifecycle cleanup.
  // Output: none.
  function importCollectionFile(file, inputElement) {
    function finishImportPicker() {
      if (!inputElement) return;
      inputElement.value = '';
      inputElement.dataset.importBusy = '';
    }

    function handleImportedText(text) {
      try {
        var parsed = JSON.parse(text);
        var collection = importPostmanCollection(parsed);
        if (collectionNameExists(collection.name)) {
          showBanner('A collection named "' + collection.name + '" already exists.', true);
          return;
        }
        setShellTab('collections');
        $.ajax({
          method: 'POST',
          url: configs.collections.save,
          dataType: 'json',
          data: {
            collection: JSON.stringify(exportPostmanCollection(collection)),
            original_name: ''
          }
        }).done(function (response) {
          try {
            requireAjaxSuccess(response, 'Import failed: the collection save route returned an empty success response.');
          } catch (error) {
            showBanner('Import failed: ' + error.message, true);
            return;
          }
          reloadCollectionsFromServer({
            failureMessage: 'Imported collection "' + collection.name + '" but failed to reload collections.',
            afterSuccess: function () {
              var imported = findCollectionByName(collection.name);
              if (imported) {
                state.selected_collection_id = imported.id;
                state.selected_request_id = '';
                persistWorkspace(true);
              }
              showBanner('Imported Postman collection "' + collection.name + '".', false);
            }
          });
        }).fail(function (xhr) {
          showBanner('Import failed: ' + (xhr.responseText || 'Unable to store imported collection.'), true);
        });
      } catch (error) {
        showBanner('Import failed: ' + error.message, true);
      } finally {
        finishImportPicker();
      }
    }

    function readCollectionFileText(fileToRead) {
      return new Promise(function (resolve, reject) {
        if (!fileToRead) {
          reject(new Error('No collection file selected.'));
          return;
        }
        if (typeof FileReader === 'function') {
          try {
            var reader = new FileReader();
            reader.onload = function () {
              resolve(reader.result);
            };
            reader.onerror = function () {
              reject(new Error('Unable to read the selected collection file.'));
            };
            reader.readAsText(fileToRead);
            return;
          } catch (error) {
            if (typeof fileToRead.text !== 'function') {
              reject(error);
              return;
            }
          }
        }
        if (typeof fileToRead.text === 'function') {
          fileToRead.text().then(resolve).catch(function () {
            reject(new Error('Unable to read the selected collection file.'));
          });
          return;
        }
        reject(new Error('Unable to read the selected collection file.'));
      });
    }

    if (!file) {
      finishImportPicker();
      return;
    }
    readCollectionFileText(file).then(handleImportedText).catch(function (error) {
      showBanner('Import failed: ' + (error && error.message ? error.message : 'Unable to read the selected collection file.'), true);
      finishImportPicker();
    });
  }

  // exportCurrentCollection()
  // Purpose: download the selected collection in Postman-compatible JSON.
  // Input: none.
  // Output: none.
  function exportCurrentCollection() {
    var collection = findCollection(state.selected_collection_id) || state.collections[0];
    if (!collection) {
      showBanner('Choose a collection to export first.', true);
      return;
    }
    setShellTab('collections');
    var payload = exportPostmanCollection(collection);
    var blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
    var url = window.URL.createObjectURL(blob);
    var link = document.createElement('a');
    link.href = url;
    link.download = collection.name.replace(/[^A-Za-z0-9._-]+/g, '-') + '.postman_collection.json';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    window.URL.revokeObjectURL(url);
  }

  // bootstrapCollectionsFromPayload(payload)
  // Purpose: normalize collection bootstrap ajax payloads.
  // Input: bootstrap payload.
  // Output: normalized collection array.
  function bootstrapCollectionsFromPayload(payload) {
    var raw = [];
    if (Array.isArray(payload)) raw = payload;
    else if (payload && Array.isArray(payload.collections)) raw = payload.collections;
    else if (payload && typeof payload === 'object') raw = Object.keys(payload).map(function (key) { return payload[key]; });
    return raw.map(importPostmanCollection).filter(Boolean);
  }

  // requireAjaxSuccess(response, fallbackMessage)
  // Purpose: reject saved Ajax responses that returned HTTP 200 without the expected ok payload.
  // Input: decoded Ajax response value and a fallback error message string.
  // Output: the original response when it reports ok, otherwise throws an Error.
  function requireAjaxSuccess(response, fallbackMessage) {
    if (response && response.ok) return response;
    if (response && response.error) throw new Error(response.error);
    throw new Error(fallbackMessage || 'The request did not report success.');
  }

  // applyBootstrapCollections(bootstrapCollections)
  // Purpose: replace the current collection set with the file-backed bootstrap collections while preserving local ids when possible.
  // Input: normalized collection array.
  // Output: none.
  function applyBootstrapCollections(bootstrapCollections) {
    var previousByName = {};
    (state.collections || []).forEach(function (collection) {
      previousByName[collection.name] = collection;
    });
    var nextCollections = (bootstrapCollections || []).map(function (collection) {
      if (previousByName[collection.name]) adoptCollectionIds(previousByName[collection.name], collection);
      return collection;
    });
    if (!nextCollections.length) nextCollections = [scratchCollection()];
    state.collections = nextCollections;
    state.tabs = (state.tabs || []).filter(function (tab) {
      return !!findCollection(tab.collection_id);
    });
    if (!state.tabs.length) {
      state.tabs = [tabFromRequest(findFirstRequest(state.collections[0]), state.collections[0].id)];
    }
    ensureActiveTab();
    if (!findCollection(state.selected_collection_id) && state.collections.length) {
      state.selected_collection_id = state.collections[0].id;
    }
    if (state.selected_request_id && !findRequestAcrossCollections(state.selected_request_id)) {
      state.selected_request_id = '';
    }
    var tab = activeTab();
    if (tab) {
      state.selected_collection_id = tab.collection_id || state.selected_collection_id;
      if (tab.request_id && !findRequestAcrossCollections(tab.request_id)) tab.request_id = '';
      if (!state.selected_request_id) state.selected_request_id = tab.request_id || '';
    }
  }

  // reloadCollectionsFromServer(options)
  // Purpose: reload all file-backed collections from the runtime ajax bootstrap endpoint and refresh the current workspace state.
  // Input: optional options hash with failureMessage.
  // Output: jqXHR-style ajax handle.
  function reloadCollectionsFromServer(options) {
    options = options || {};
    return $.ajax({
      method: 'GET',
      url: configs.collections.bootstrap,
      dataType: 'json'
    }).done(function (payload) {
      try {
        if (!payload || typeof payload !== 'object' || !Array.isArray(payload.collections)) {
          throw new Error('The collection bootstrap response was not valid JSON.');
        }
        applyBootstrapCollections(bootstrapCollectionsFromPayload(payload));
        applyRoute(readRoute(), { allow_open: true });
        if (typeof options.afterSuccess === 'function') options.afterSuccess(payload);
        persistWorkspace(true);
      } catch (error) {
        showBanner('Collection bootstrap failed: ' + error.message, true);
      }
      if (payload && payload.errors && payload.errors.length) showBanner(payload.errors.join(' | '), true);
      renderAll();
    }).fail(function (xhr) {
      showBanner(options.failureMessage || ('Collection bootstrap failed: ' + (xhr.responseText || 'unknown error')), true);
      renderAll();
    });
  }

  // persistCollectionToServer(collection, options)
  // Purpose: write one collection to config/api-dashboard as Postman JSON.
  // Input: local collection hash and optional options hash with original_name, successMessage, and failureMessage.
  // Output: jqXHR-style ajax handle.
  function persistCollectionToServer(collection, options) {
    options = options || {};
    return $.ajax({
      method: 'POST',
      url: configs.collections.save,
      dataType: 'json',
      data: {
        collection: JSON.stringify(exportPostmanCollection(collection)),
        original_name: options.original_name || ''
      }
    }).done(function (response) {
      try {
        requireAjaxSuccess(response, 'Collection save failed: the save route returned an empty success response.');
        persistWorkspace(true);
        renderAll();
        showBanner(options.successMessage || ('Saved collection "' + collection.name + '".'), false);
      } catch (error) {
        reloadCollectionsFromServer({
          failureMessage: options.failureMessage || ('Collection save failed: ' + error.message)
        });
      }
    }).fail(function (xhr) {
      reloadCollectionsFromServer({
        failureMessage: options.failureMessage || ('Collection save failed: ' + (xhr.responseText || 'unknown error'))
      });
    });
  }

  // deleteCollectionFromServer(name, options)
  // Purpose: delete one stored collection file from config/api-dashboard.
  // Input: collection name string and optional options hash with successMessage and failureMessage.
  // Output: jqXHR-style ajax handle.
  function deleteCollectionFromServer(name, options) {
    options = options || {};
    return $.ajax({
      method: 'POST',
      url: configs.collections.delete,
      dataType: 'json',
      data: {
        name: name
      }
    }).done(function (response) {
      try {
        requireAjaxSuccess(response, 'Collection delete failed: the delete route returned an empty success response.');
        persistWorkspace(true);
        renderAll();
        showBanner(options.successMessage || ('Deleted collection "' + name + '".'), false);
      } catch (error) {
        reloadCollectionsFromServer({
          failureMessage: options.failureMessage || ('Collection delete failed: ' + error.message)
        });
      }
    }).fail(function (xhr) {
      reloadCollectionsFromServer({
        failureMessage: options.failureMessage || ('Collection delete failed: ' + (xhr.responseText || 'unknown error'))
      });
    });
  }

  // sendActiveRequest()
  // Purpose: dispatch the active request through the saved Ajax LWP sender.
  // Input: none.
  // Output: none.
  function sendActiveRequest() {
    syncActiveTabFromForm();
    setShellTab('workspace');
    setResponseTab('body');
    var tab = activeTab();
    var collection = findCollection(state.selected_collection_id) || findCollection(tab.collection_id) || state.collections[0];
    if (!tab || !tab.request.url) {
      renderResponse({ ok: false, error: 'Enter a request URL first.' });
      return;
    }
    renderResponse({ ok: false, error: 'Sending request...' });
    var payload = clone(tab.request);
    payload.url = applyVariables(collection, payload.url);
    payload.headers_text = applyVariables(collection, payload.headers_text);
    payload.body = applyVariables(collection, payload.body);
    $.ajax({
      method: 'POST',
      url: configs.send.request,
      dataType: 'json',
      data: {
        settings: JSON.stringify(payload)
      }
    }).done(function (response) {
      tab.response = normalizeResponse(response);
      saveState();
      renderResponse(tab.response);
    }).fail(function (xhr) {
      tab.response = normalizeResponse({ ok: false, error: xhr.responseText || 'Request dispatch failed.' });
      saveState();
      renderResponse(tab.response);
    });
  }

  // bindForm()
  // Purpose: attach browser event handlers to the workspace controls.
  // Input: none.
  // Output: none.
  function bindForm() {
    ['api-request-name', 'api-request-method', 'api-request-url', 'api-request-headers', 'api-request-body', 'api-request-description', 'api-timeout', 'api-follow-redirects', 'api-insecure-tls', 'api-request-variables'].forEach(function (id) {
      var element = document.getElementById(id);
      var eventName = element && element.tagName === 'SELECT' ? 'change' : 'input';
      if (!element) return;
      element.addEventListener(eventName, syncActiveTabFromForm);
      if (eventName !== 'change') element.addEventListener('change', syncActiveTabFromForm);
    });
    document.getElementById('api-new-collection').addEventListener('click', createCollection);
    document.getElementById('api-rename-collection').addEventListener('click', renameSelectedCollection);
    document.getElementById('api-delete-collection').addEventListener('click', deleteSelectedCollection);
    document.getElementById('api-new-tab').addEventListener('click', createBlankTab);
    document.getElementById('api-duplicate-tab').addEventListener('click', duplicateActiveTab);
    document.getElementById('api-save-request').addEventListener('click', saveRequestToCollection);
    document.getElementById('api-delete-request').addEventListener('click', deleteSavedRequest);
    document.getElementById('api-export-collection').addEventListener('click', exportCurrentCollection);
    document.getElementById('api-send-request').addEventListener('click', sendActiveRequest);
    document.getElementById('api-clear-response').addEventListener('click', function () {
      setResponseTab('body');
      var tab = activeTab();
      if (tab) tab.response = null;
      saveState();
      renderResponse(null);
      showBanner('', false);
    });
    function importFromPicker(event) {
      if (event.target.dataset.importBusy === '1') return;
      var file = event.target.files && event.target.files[0];
      if (!file) return;
      event.target.dataset.importBusy = '1';
      importCollectionFile(file, event.target);
    }
    document.getElementById('api-import-file').addEventListener('change', importFromPicker);
    ['collections', 'workspace'].forEach(function (name) {
      document.getElementById('api-shell-tab-' + name).addEventListener('click', function () {
        setShellTab(name);
        renderShellTabs();
      });
    });
    ['request', 'body', 'headers'].forEach(function (name) {
      document.getElementById('api-response-tab-' + name).addEventListener('click', function () {
        setResponseTab(name);
        renderResponseTabs();
      });
    });
    window.addEventListener('popstate', function () {
      applyRoute(readRoute(), { allow_open: true });
      persistWorkspace(true);
      renderAll();
    });
  }

  // renderAll()
  // Purpose: render the full workspace UI from the current state.
  // Input: none.
  // Output: none.
  function renderAll() {
    renderShellTabs();
    renderCollections();
    renderTabs();
    renderEditor();
    renderResponseTabs();
  }

  $(document).ready(function () {
    state = loadState();
    bindForm();
    applyRoute(readRoute(), { allow_open: true });
    persistWorkspace(true);
    renderAll();
    reloadCollectionsFromServer();
  });
}());
</script>
:--------------------------------------------------------------------------------:
CODE1: Ajax jvar => 'configs.collections.bootstrap', type => 'json', file => 'api-dashboard-bootstrap', code => q{
  BEGIN {
    my $ajax_file = $ENV{DEVELOPER_DASHBOARD_AJAX_FILE} || '';
    if ( $ajax_file ne '' ) {
      require File::Basename;
      require File::Spec;
      require lib;
      my $root = File::Basename::dirname($ajax_file);
      $root = File::Basename::dirname($root) for 1 .. 4;
      my $candidate = File::Spec->catdir( $root, 'lib' );
      lib->import($candidate) if -d $candidate;
    }
  }

  use File::Path qw(make_path);
  use File::Spec;
  use Developer::Dashboard::Folder ();
  use Developer::Dashboard::JSON qw(json_decode json_encode);
  use Developer::Dashboard::PathRegistry ();

  sub _api_dashboard_collection_root {
    my $dir = File::Spec->catdir( Developer::Dashboard::Folder->configs, 'api-dashboard' );
    make_path($dir) if $dir ne '' && !-d $dir;
    Developer::Dashboard::PathRegistry->new->secure_dir_permissions($dir);
    return $dir;
  }

  sub _api_dashboard_collection_name {
    my ($collection) = @_;
    die 'Collection info hash is required' if ref( $collection->{info} ) ne 'HASH';
    my $name = defined $collection->{info}{name} ? $collection->{info}{name} : '';
    $name =~ s/^\s+//;
    $name =~ s/\s+\z//;
    die 'Collection info.name is required' if $name eq '';
    return $name;
  }

  sub _api_dashboard_coerce_collection {
    my ($input) = @_;
    die 'Collection payload must be a hash reference' if ref($input) ne 'HASH';
    my $collection = ref( $input->{collection} ) eq 'HASH' ? $input->{collection} : $input;
    die 'Collection payload must be a hash reference' if ref($collection) ne 'HASH';
    my $name = _api_dashboard_collection_name($collection);
    $collection->{info}{name} = $name;
    $collection->{info}{schema} ||= 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json';
    $collection->{item} = [] if ref( $collection->{item} ) ne 'ARRAY';
    $collection->{variable} = [] if ref( $collection->{variable} ) ne 'ARRAY';
    return $collection;
  }

  sub _api_dashboard_slurp {
    my ($file) = @_;
    open my $fh, '<:raw', $file or die "Unable to read $file: $!";
    local $/;
    my $raw = <$fh>;
    close $fh or die "Unable to close $file: $!";
    return $raw;
  }

  my $dir = _api_dashboard_collection_root();
  my @collections;
  my @errors;

  my $dh;
  if ( !opendir( $dh, $dir ) ) {
    print json_encode(
      {
        collections => [],
        errors      => ["Unable to read api-dashboard collection directory $dir: $!"],
      }
    );
    return;
  }

  for my $entry ( sort readdir $dh ) {
    next if $entry eq '.' || $entry eq '..';
    next if $entry !~ /\.json\z/i;

    my $path = File::Spec->catfile( $dir, $entry );
    next if !-f $path;

    my $raw = eval { _api_dashboard_slurp($path) };
    if ($@) {
      push @errors, $@;
      next;
    }

    my $parsed = eval { json_decode($raw) };
    if ($@) {
      push @errors, "Unable to parse Postman JSON in $entry";
      next;
    }

    my $collection = eval { _api_dashboard_coerce_collection($parsed) };
    if ($@) {
      push @errors, "Skipping $entry because it does not contain a Postman collection";
      next;
    }

    push @collections, $collection;
  }

  closedir $dh or push @errors, "Unable to close api-dashboard collection directory $dir: $!";

  print json_encode(
    {
      collections => \@collections,
      errors      => \@errors,
    }
  );
};
:--------------------------------------------------------------------------------:
CODE2: Ajax jvar => 'configs.collections.save', type => 'json', file => 'api-dashboard-collections-save', code => q{
  BEGIN {
    my $ajax_file = $ENV{DEVELOPER_DASHBOARD_AJAX_FILE} || '';
    if ( $ajax_file ne '' ) {
      require File::Basename;
      require File::Spec;
      require lib;
      my $root = File::Basename::dirname($ajax_file);
      $root = File::Basename::dirname($root) for 1 .. 4;
      my $candidate = File::Spec->catdir( $root, 'lib' );
      lib->import($candidate) if -d $candidate;
    }
  }

  use File::Basename qw(basename dirname);
  use File::Path qw(make_path);
  use File::Spec;
  use Developer::Dashboard::Folder ();
  use Developer::Dashboard::JSON qw(json_decode json_encode);
  use Developer::Dashboard::PathRegistry ();

  sub _api_dashboard_paths {
    return Developer::Dashboard::PathRegistry->new;
  }

  sub _api_dashboard_collection_root {
    my $dir = File::Spec->catdir( Developer::Dashboard::Folder->configs, 'api-dashboard' );
    make_path($dir) if $dir ne '' && !-d $dir;
    _api_dashboard_paths()->secure_dir_permissions($dir);
    return $dir;
  }

  sub _api_dashboard_collection_name {
    my ($collection) = @_;
    die 'Collection info hash is required' if ref( $collection->{info} ) ne 'HASH';
    my $name = defined $collection->{info}{name} ? $collection->{info}{name} : '';
    $name =~ s/^\s+//;
    $name =~ s/\s+\z//;
    die 'Collection info.name is required' if $name eq '';
    return $name;
  }

  sub _api_dashboard_safe_filename {
    my ($name) = @_;
    $name = '' if !defined $name;
    $name =~ s/[\x00-\x1F]+/ /g;
    $name =~ s{[\\/:*?"<>|]+}{-}g;
    $name =~ s/\s+/ /g;
    $name =~ s/^\s+//;
    $name =~ s/\s+\z//;
    die 'Collection name is required' if $name eq '' || $name eq '.' || $name eq '..';
    return $name;
  }

  sub _api_dashboard_file_for_name {
    my ($name) = @_;
    return File::Spec->catfile( _api_dashboard_collection_root(), _api_dashboard_safe_filename($name) . '.json' );
  }

  sub _api_dashboard_coerce_collection {
    my ($input) = @_;
    die 'Collection payload must be a hash reference' if ref($input) ne 'HASH';
    my $collection = ref( $input->{collection} ) eq 'HASH' ? $input->{collection} : $input;
    die 'Collection payload must be a hash reference' if ref($collection) ne 'HASH';
    my $name = _api_dashboard_collection_name($collection);
    $collection->{info}{name} = $name;
    $collection->{info}{schema} ||= 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json';
    $collection->{item} = [] if ref( $collection->{item} ) ne 'ARRAY';
    $collection->{variable} = [] if ref( $collection->{variable} ) ne 'ARRAY';
    return $collection;
  }

  sub _api_dashboard_atomic_write_text {
    my ( $file, $text ) = @_;
    my $tmp = "$file.pending";
    my $dir = dirname($file);
    make_path($dir) if !-d $dir;
    _api_dashboard_paths()->secure_dir_permissions($dir);
    open my $fh, '>:raw', $tmp or die "Unable to write $tmp: $!";
    print {$fh} defined $text ? $text : '';
    close $fh or die "Unable to close $tmp: $!";
    _api_dashboard_paths()->secure_file_permissions($tmp);
    unlink $file if -f $file;
    rename $tmp, $file or die "Unable to rename $tmp to $file: $!";
    _api_dashboard_paths()->secure_file_permissions($file);
    return $file;
  }

  my $collection_raw = params->{collection} // '';
  die "collection payload is required" if $collection_raw eq '';
  my $collection = json_decode($collection_raw);
  die "collection payload must be a hash" if ref($collection) ne 'HASH';

  my $stored = _api_dashboard_coerce_collection($collection);
  my $name = _api_dashboard_collection_name($stored);
  my $file = _api_dashboard_file_for_name($name);
  my $original_name = scalar( params->{original_name} // '' );
  my $original_file = $original_name ne '' ? _api_dashboard_file_for_name($original_name) : '';

  die "Collection '$name' already exists"
    if $original_name ne '' && -f $file && $original_file ne $file;

  _api_dashboard_atomic_write_text( $file, json_encode($stored) );

  if ( $original_file ne '' && $original_file ne $file && -f $original_file ) {
    unlink $original_file or die "Unable to remove renamed collection file $original_file: $!";
  }

  print json_encode(
    {
      ok         => 1,
      collection => $stored,
      filename   => basename($file),
      path       => $file,
    }
  );
};
:--------------------------------------------------------------------------------:
CODE3: Ajax jvar => 'configs.collections.delete', type => 'json', file => 'api-dashboard-collections-delete', code => q{
  BEGIN {
    my $ajax_file = $ENV{DEVELOPER_DASHBOARD_AJAX_FILE} || '';
    if ( $ajax_file ne '' ) {
      require File::Basename;
      require File::Spec;
      require lib;
      my $root = File::Basename::dirname($ajax_file);
      $root = File::Basename::dirname($root) for 1 .. 4;
      my $candidate = File::Spec->catdir( $root, 'lib' );
      lib->import($candidate) if -d $candidate;
    }
  }

  use File::Basename qw(basename);
  use File::Path qw(make_path);
  use File::Spec;
  use Developer::Dashboard::Folder ();
  use Developer::Dashboard::JSON qw(json_encode);
  use Developer::Dashboard::PathRegistry ();

  sub _api_dashboard_collection_root {
    my $dir = File::Spec->catdir( Developer::Dashboard::Folder->configs, 'api-dashboard' );
    make_path($dir) if $dir ne '' && !-d $dir;
    Developer::Dashboard::PathRegistry->new->secure_dir_permissions($dir);
    return $dir;
  }

  sub _api_dashboard_safe_filename {
    my ($name) = @_;
    $name = '' if !defined $name;
    $name =~ s/[\x00-\x1F]+/ /g;
    $name =~ s{[\\/:*?"<>|]+}{-}g;
    $name =~ s/\s+/ /g;
    $name =~ s/^\s+//;
    $name =~ s/\s+\z//;
    die 'Collection name is required' if $name eq '' || $name eq '.' || $name eq '..';
    return $name;
  }

  sub _api_dashboard_file_for_name {
    my ($name) = @_;
    return File::Spec->catfile( _api_dashboard_collection_root(), _api_dashboard_safe_filename($name) . '.json' );
  }

  my $name = params->{name} // '';
  die "collection name is required" if $name eq '';

  my $file = _api_dashboard_file_for_name($name);
  die "Collection '$name' does not exist" if !-f $file;

  unlink $file or die "Unable to remove collection file $file: $!";

  print json_encode(
    {
      ok       => 1,
      filename => basename($file),
      path     => $file,
    }
  );
};
:--------------------------------------------------------------------------------:
CODE4: Ajax jvar => 'configs.send.request', type => 'json', file => 'api-dashboard-send-request', code => q{
  use HTTP::Request;
  use LWP::Protocol::https ();
  use LWP::UserAgent;
  use MIME::Base64 qw(encode_base64);
  use Time::HiRes qw(time);
  use URI ();
  use Developer::Dashboard::DataHelper qw(j je);

  sub _api_dashboard_headers {
    my ($text) = @_;
    my @pairs;
    for my $line ( split /\n/, ( defined $text ? $text : '' ) ) {
      $line =~ s/\r//g;
      next if $line !~ /\S/;
      my ( $key, $value ) = split /\s*:\s*/, $line, 2;
      die "Invalid header line: $line" if !defined $key || !defined $value || $key eq '';
      push @pairs, [ $key, $value ];
    }
    return \@pairs;
  }

  sub _api_dashboard_is_textual {
    my ( $content_type, $content ) = @_;
    return 1 if ( $content_type || '' ) =~ m{(?:json|xml|javascript|html|text|x-www-form-urlencoded)}i;
    return 0 if !defined $content;
    return $content !~ /[\x00-\x08\x0B\x0C\x0E-\x1F]/;
  }

  sub _api_dashboard_preview_media_type {
    my ($content_type) = @_;
    my $type = lc( $content_type || '' );
    $type =~ s/\s*;.*\z//;
    return '' if $type eq '';
    return $type if $type eq 'application/pdf';
    return $type if $type =~ /\Aimage\/(?:png|jpe?g|gif|webp|bmp|svg\+xml|tiff?)\z/;
    return '';
  }

  my $payload = { ok => 0 };

  my $ok = eval {
    my $settings_raw = params->{settings} // '';
    die "settings payload is required" if $settings_raw eq '';
    my $settings = je $settings_raw;
    die "settings payload must be a hash" if ref($settings) ne 'HASH';

    my $method = uc( $settings->{method} || 'GET' );
    die "Unsupported HTTP method: $method" if $method !~ /\A(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\z/;

    my $url = $settings->{url} // '';
    die "Request URL is required" if $url eq '';
    my $uri = URI->new($url);
    die "Request URL must use http or https" if ( ( $uri->scheme || '' ) !~ /\Ahttps?\z/i );

    my $timeout_s = int( $settings->{timeout_s} || 120 );
    $timeout_s = 120 if $timeout_s < 1 || $timeout_s > 600;
    my $allow_insecure  = $settings->{insecure_tls} ? 1 : 0;
    my $follow_redirect = exists $settings->{follow_redirects} ? ( $settings->{follow_redirects} ? 1 : 0 ) : 1;

    my $ua = LWP::UserAgent->new(
      agent             => 'Developer Dashboard API Dashboard/1.60',
      env_proxy         => 1,
      timeout           => $timeout_s,
      max_redirect      => $follow_redirect ? 7 : 0,
      protocols_allowed => [qw(http https)],
      ssl_opts          => $allow_insecure
        ? { verify_hostname => 0, SSL_verify_mode => 0x00 }
        : { verify_hostname => 1 },
    );

    my $request = HTTP::Request->new( $method => $uri->as_string );
    my $header_pairs = _api_dashboard_headers( $settings->{headers_text} );
    for my $pair ( @{$header_pairs} ) {
      $request->header( $pair->[0] => $pair->[1] );
    }
    $request->content( $settings->{body} ) if defined $settings->{body} && $settings->{body} ne '';

    my $started  = time;
    my $response = $ua->request($request);
    my $elapsed_ms = int( ( time - $started ) * 1000 );
    my @headers = map { +{ key => $_, value => scalar $response->header($_) } } $response->header_field_names;
    my $content_type = $response->header('Content-Type') || '';
    my $preview_media_type = _api_dashboard_preview_media_type($content_type);
    my $raw_body = $response->content;
    my $body_mode = 'text';
    my $body = '';
    my $preview_url = '';
    if ($preview_media_type) {
      $body_mode = 'preview';
      $body = 'Preview available.';
      $preview_url = 'data:' . $preview_media_type . ';base64,' . encode_base64( $raw_body, '' );
    }
    elsif ( ( $content_type || '' ) =~ /json/i ) {
      my $decoded = $response->decoded_content;
      my $pretty = eval { j je $decoded };
      $body_mode = 'json';
      $body = defined $pretty && $pretty ne '' ? $pretty : $decoded;
    }
    elsif ( _api_dashboard_is_textual( $content_type, $raw_body ) ) {
      $body_mode = 'text';
      $body = $response->decoded_content;
    }
    else {
      $body_mode = 'text';
      $body = 'Binary response omitted because it is not a previewable media type.';
    }

    $payload = {
      ok      => $response->is_success ? 1 : 0,
      error   => $response->is_success ? '' : $response->status_line,
      request => {
        method           => $method,
        url              => $uri->as_string,
        headers_text     => $settings->{headers_text} // '',
        body             => $settings->{body} // '',
        timeout_s        => $timeout_s,
        follow_redirects => $follow_redirect ? 1 : 0,
        insecure_tls     => $allow_insecure ? 1 : 0,
      },
      response => {
        status             => $response->code,
        reason             => $response->message,
        elapsed_ms         => $elapsed_ms,
        content_type       => $content_type,
        headers            => \@headers,
        body               => $body,
        body_mode          => $body_mode,
        preview_media_type => $preview_media_type,
        preview_url        => $preview_url,
        body_size_bytes    => length($raw_body),
      },
    };
    1;
  };
  if ( !$ok ) {
    my $error = $@ || 'Unknown API dashboard send failure';
    $error =~ s/\s+\z//;
    $payload = {
      ok    => 0,
      error => $error,
    };
  }

  print j $payload;
};
BOOKMARK
}

# _sql_dashboard_page()
# Builds the seeded SQL dashboard bookmark page with generic DBI profile management, schema browsing, and programmable SQL execution.
# Input: none.
# Output: Developer::Dashboard::PageDocument object.
sub _sql_dashboard_page {
    my $section = ":--------------------------------------------------------------------------------:\n";
    my $instruction = ''
      . "TITLE: SQL Dashboard\n"
      . $section
      . "BOOKMARK: sql-dashboard\n"
      . $section
      . "STASH:\n"
      . "  SQLS_SEP => ':------------------------------------------------------------------------------:'\n"
      . "  INSTRUCTION_SEP => ':~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~:'\n"
      . $section
      . "HTML: " . _sql_dashboard_html() . "\n"
      . $section
      . "CODE1: Ajax jvar => 'configs.profiles.bootstrap', type => 'json', singleton => 'SQL_DASHBOARD_PROFILES_BOOTSTRAP', file => 'sql-dashboard-profiles-bootstrap', code => q{\n" . _sql_dashboard_profiles_bootstrap_code() . "\n};\n"
      . $section
      . "CODE2: Ajax jvar => 'configs.profiles.save', type => 'json', singleton => 'SQL_DASHBOARD_PROFILES_SAVE', file => 'sql-dashboard-profiles-save', code => q{\n" . _sql_dashboard_profiles_save_code() . "\n};\n"
      . $section
      . "CODE3: Ajax jvar => 'configs.profiles.delete', type => 'json', singleton => 'SQL_DASHBOARD_PROFILES_DELETE', file => 'sql-dashboard-profiles-delete', code => q{\n" . _sql_dashboard_profiles_delete_code() . "\n};\n"
      . $section
      . "CODE4: Ajax jvar => 'configs.collections.save', type => 'json', singleton => 'SQL_DASHBOARD_COLLECTIONS_SAVE', file => 'sql-dashboard-collections-save', code => q{\n" . _sql_dashboard_collections_save_code() . "\n};\n"
      . $section
      . "CODE5: Ajax jvar => 'configs.collections.delete', type => 'json', singleton => 'SQL_DASHBOARD_COLLECTIONS_DELETE', file => 'sql-dashboard-collections-delete', code => q{\n" . _sql_dashboard_collections_delete_code() . "\n};\n"
      . $section
      . "CODE6: Ajax jvar => 'configs.sql.execute', type => 'json', singleton => 'SQL_DASHBOARD_EXECUTE', file => 'sql-dashboard-execute', code => q{\n" . _sql_dashboard_execute_code() . "\n};\n"
      . $section
      . "CODE7: Ajax jvar => 'configs.schema.browse', type => 'json', singleton => 'SQL_DASHBOARD_SCHEMA_BROWSE', file => 'sql-dashboard-schema-browse', code => q{\n" . _sql_dashboard_schema_code() . "\n};\n";
    return Developer::Dashboard::PageDocument->from_instruction($instruction);
}

# _sql_dashboard_html()
# Builds the seeded SQL dashboard HTML, CSS, and browser logic.
# Input: none.
# Output: HTML source string.
sub _sql_dashboard_html {
    return <<'HTML';
<style>
.sql-shell { display:grid; gap:18px; color:#24313c; }
.sql-banner { padding:12px 14px; border:1px solid #d5c36a; background:#fff6d6; color:#563f00; }
.sql-banner.is-error { border-color:#d89a9a; background:#fff1f1; color:#7f1d1d; }
.sql-main-tabs, .sql-subtabs { display:flex; gap:0; flex-wrap:wrap; align-items:flex-end; border-bottom:1px solid #aeb9c3; }
.sql-main-tab, .sql-subtab { display:inline-block; margin:0 4px -1px 0; padding:10px 14px; border:1px solid #aeb9c3; border-bottom:1px solid #aeb9c3; background:linear-gradient(180deg, #f6f8fa 0%, #dfe7ef 100%); color:#20303b; text-decoration:none; font-weight:700; }
.sql-main-tab.is-active, .sql-subtab.is-active { background:#ffffff; border-bottom-color:#ffffff; position:relative; top:1px; }
.sql-panel { display:none; border:1px solid #bcc7cf; background:#ffffff; padding:16px; }
.sql-panel.is-active { display:block; }
.sql-grid { display:grid; gap:16px; }
.sql-two-col { grid-template-columns: minmax(320px, 1fr) minmax(360px, 2fr); }
.sql-card { border:1px solid #d8dee3; background:#fbfcfd; padding:14px; }
.sql-card h3 { margin-top:0; }
.sql-form-grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap:12px; }
.sql-form-grid label, .sql-stack label { display:grid; gap:6px; font-weight:600; color:#24313c; }
.sql-form-grid input, .sql-form-grid textarea, .sql-stack textarea, .sql-stack select, .sql-stack input { width:100%; box-sizing:border-box; padding:10px 12px; border:1px solid #b8c1c9; background:#fff; font-family:monospace; }
.sql-stack { display:grid; gap:12px; }
.sql-toolbar { display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin-bottom:12px; }
.sql-action { display:inline-flex; align-items:center; gap:4px; padding:2px 0; border:none; background:transparent; color:#345068; text-decoration:none; font-weight:600; font-size:13px; line-height:1.3; }
.sql-action:hover, .sql-action:focus { color:#102131; text-decoration:underline; }
.sql-action.sql-action-quiet { color:#617181; font-weight:500; }
.sql-badge { display:inline-block; padding:4px 8px; border:1px solid #d5dde3; background:#f8fafb; color:#465560; font-size:12px; }
.sql-workspace-layout { display:grid; grid-template-columns: minmax(250px, 300px) minmax(0, 1fr); border:1px solid #cbd3da; background:#f7fafc; }
.sql-workspace-nav { display:grid; gap:16px; align-content:start; padding:14px; border-right:1px solid #cbd3da; background:linear-gradient(180deg, #eef3f7 0%, #e6edf3 100%); }
.sql-workspace-editor { display:grid; gap:14px; align-content:start; padding:18px; background:#fbfcfd; }
.sql-nav-section { display:grid; gap:10px; }
.sql-nav-section h3, .sql-nav-section h4 { margin:0; }
.sql-nav-header { display:flex; gap:10px; align-items:center; justify-content:space-between; }
.sql-list { border:1px solid #c2ccd4; background:#ffffff; }
.sql-list-row { display:grid; grid-template-columns: minmax(0, 1fr) auto; gap:8px; align-items:start; padding:0; border-top:1px solid #d7dee4; }
.sql-list-row:first-child { border-top:none; }
.sql-list-row.is-active { background:#dce8f3; }
.sql-list-item { display:block; padding:10px 12px; color:#1d2b35; text-decoration:none; min-width:0; }
.sql-list-item.is-active { font-weight:700; }
.sql-list-item small { display:block; margin-top:4px; color:#5b6873; font-weight:400; }
.sql-inline-delete { display:inline-flex; align-items:center; justify-content:center; min-width:30px; padding:10px 8px; color:#7a2030; text-decoration:none; font-size:12px; font-weight:700; }
.sql-inline-delete:hover, .sql-inline-delete:focus { text-decoration:none; color:#5a0f20; background:#f8dde2; }
.sql-inline-fields { display:grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap:12px; }
.sql-editor-frame { display:grid; gap:10px; border:1px solid #d2dae0; background:#ffffff; padding:12px; }
.sql-editor-frame label { display:grid; gap:8px; font-weight:700; color:#24313c; }
.sql-editor-frame textarea { width:100%; min-height:240px; max-height:75vh; box-sizing:border-box; padding:12px 14px; border:1px solid #b8c1c9; background:#fff; font-family:monospace; line-height:1.5; resize:none; overflow:hidden; }
.sql-editor-actions { display:flex; gap:12px; flex-wrap:wrap; align-items:center; justify-content:space-between; padding-top:8px; border-top:1px solid #dbe2e8; }
.sql-editor-actions-left { display:flex; gap:12px; flex-wrap:wrap; align-items:center; }
.sql-editor-note { color:#63727e; font-size:12px; }
.sql-result-info { min-height:120px; padding:12px; border:1px solid #c5d0d8; background:#16212a; color:#edf3f7; white-space:pre-wrap; overflow:auto; }
.sql-result-html { margin-top:12px; overflow:auto; }
.sql-result-html table { width:100%; border-collapse:collapse; }
.sql-result-html th, .sql-result-html td { border:1px solid #d0d8df; padding:8px 10px; text-align:left; vertical-align:top; }
.sql-result-html th { background:#eef3f7; }
.sql-help { margin:0; color:#4b5a66; font-size:13px; }
.sql-schema-grid { display:grid; grid-template-columns: minmax(260px, 1fr) minmax(320px, 1fr); gap:16px; }
.sql-empty { padding:12px; border:1px dashed #bcc7cf; background:#f8fafb; color:#55636d; }
.sql-checkbox { display:flex; gap:10px; align-items:center; font-weight:600; color:#24313c; }
@media (max-width: 920px) {
  .sql-two-col, .sql-schema-grid, .sql-workspace-layout { grid-template-columns: 1fr; }
  .sql-workspace-nav { border-right:none; border-bottom:1px solid #cbd3da; }
  .sql-editor-actions { align-items:flex-start; }
}
</style>
<div class="sql-shell">
  <div id="sql-banner" class="sql-banner" hidden></div>
  <nav class="sql-main-tabs" aria-label="SQL Dashboard Sections">
    <a href="#" class="sql-main-tab is-active" data-sql-main-tab="profiles" role="tab" aria-selected="true">Connection Profiles</a>
    <a href="#" class="sql-main-tab" data-sql-main-tab="workspace" role="tab" aria-selected="false">SQL Workspace</a>
    <a href="#" class="sql-main-tab" data-sql-main-tab="schema" role="tab" aria-selected="false">Schema Explorer</a>
  </nav>

  <section id="sql-panel-profiles" class="sql-panel is-active">
    <div class="sql-grid sql-two-col">
      <div class="sql-card">
        <div class="sql-toolbar">
          <a href="#" id="sql-profile-new" class="sql-action">New Profile</a>
          <a href="#" id="sql-profile-save" class="sql-action">Save Profile</a>
          <a href="#" id="sql-profile-delete" class="sql-action">Delete Profile</a>
        </div>
        <div id="sql-profile-tabs" class="sql-subtabs" aria-label="Saved Connection Profiles"></div>
        <div id="sql-profile-tabs-empty" class="sql-empty">No saved profiles yet. Create one and install a driver with <code>dashboard cpan DBD::Driver</code>.</div>
      </div>
      <div class="sql-card">
        <h3>Connection Profiles</h3>
        <div class="sql-form-grid">
          <label>Profile Name
            <input id="sql-profile-name" type="text" placeholder="Reporting DB">
          </label>
          <label>Driver Module
            <select id="sql-profile-driver"></select>
          </label>
          <label>DSN
            <input id="sql-profile-dsn" type="text" placeholder="dbi:SQLite:dbname=/tmp/demo.db">
          </label>
          <label>User
            <input id="sql-profile-user" type="text" placeholder="demo">
          </label>
          <label>Password
            <input id="sql-profile-password" type="password" placeholder="Stored only when requested">
          </label>
          <label>DBI Attrs JSON
            <input id="sql-profile-attrs" type="text" placeholder='{"RaiseError":1,"PrintError":0,"AutoCommit":1}'>
          </label>
        </div>
        <label class="sql-checkbox"><input id="sql-profile-save-password" type="checkbox"> Save password to the local runtime profile file</label>
        <p class="sql-help">Profiles are stored under <code>.developer-dashboard/config/sql-dashboard/&lt;profile-name&gt;.json</code>. Shared URLs only carry the portable <code>connection</code> id built from DSN and user.</p>
      </div>
    </div>
  </section>

  <section id="sql-panel-workspace" class="sql-panel">
    <div class="sql-workspace-layout">
      <aside id="sql-workspace-nav" class="sql-workspace-nav">
        <div class="sql-nav-section">
          <div class="sql-toolbar">
            <a href="#" id="sql-collection-new" class="sql-action">New Collection</a>
            <a href="#" id="sql-collection-save" class="sql-action">Save Collection</a>
            <a href="#" id="sql-collection-delete" class="sql-action">Delete Collection</a>
          </div>
          <label>Collection Name
            <input id="sql-collection-name" type="text" placeholder="Reporting Queries">
          </label>
          <div id="sql-collection-tabs" class="sql-subtabs" aria-label="Saved SQL Collections"></div>
          <div id="sql-collection-tabs-empty" class="sql-empty">No saved SQL collections yet. Collections stay separate from connection profiles.</div>
        </div>
        <div class="sql-nav-section">
          <div class="sql-nav-header">
            <strong id="sql-collection-item-list-title">Saved SQL</strong>
            <a href="#" id="sql-collection-item-new" class="sql-action sql-action-quiet">New SQL</a>
          </div>
          <div id="sql-collection-item-list" class="sql-list" aria-label="Saved SQL Entries"></div>
          <div id="sql-collection-item-tabs-empty" class="sql-empty">Select a collection to view its saved SQL entries.</div>
          <span class="sql-badge">Collections and connection profiles are intentionally unrelated</span>
        </div>
      </aside>
      <section id="sql-workspace-editor" class="sql-workspace-editor">
        <div class="sql-toolbar">
          <span id="sql-active-profile" class="sql-badge">No active profile selected</span>
          <span id="sql-active-collection" class="sql-badge">No active collection</span>
          <span id="sql-active-sql-name" class="sql-badge">Unsaved SQL</span>
        </div>
        <div class="sql-inline-fields">
          <label>SQL Name
            <input id="sql-collection-item-name" type="text" placeholder="Users By Status">
          </label>
        </div>
        <div class="sql-editor-frame">
          <label>SQL
            <textarea id="sql-editor" rows="12" placeholder="select * from users"></textarea>
          </label>
          <div id="sql-editor-actions" class="sql-editor-actions">
            <div class="sql-editor-actions-left">
              <a href="#" id="sql-run" class="sql-action">&#127939; Run SQL</a>
              <a href="#" id="sql-collection-item-save" class="sql-action">&#128190; Save SQL</a>
            </div>
            <span id="sql-editor-note" class="sql-editor-note">Edit SQL above, then use save to keep it in a collection or run to execute it with the active connection.</span>
          </div>
        </div>
        <p class="sql-help">Use <code>SQLS_SEP</code> (<code>:------------------------------------------------------------------------------:</code>) between statements and <code>INSTRUCTION_SEP</code> (<code>:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~:</code>) for <code>STASH</code>, <code>ROW</code>, <code>BEFORE</code>, and <code>AFTER</code> hooks.</p>
        <pre id="sql-result-info" class="sql-result-info">Select a profile and run SQL.</pre>
        <div id="sql-result-html" class="sql-result-html"></div>
      </section>
    </div>
  </section>

  <section id="sql-panel-schema" class="sql-panel">
    <div class="sql-card">
      <div class="sql-toolbar">
        <span id="sql-schema-profile" class="sql-badge">Schema Explorer</span>
        <a href="#" id="sql-schema-refresh" class="sql-action">Refresh Schema</a>
      </div>
      <div class="sql-schema-grid">
        <div>
          <h3>Tables</h3>
          <div id="sql-table-tabs" class="sql-subtabs"></div>
          <div id="sql-table-tabs-empty" class="sql-empty">Choose a profile and refresh the schema to load table tabs.</div>
        </div>
        <div>
          <h3>Columns</h3>
          <div id="sql-column-list" class="sql-empty">Select a table tab to view columns.</div>
        </div>
      </div>
    </div>
  </section>
</div>
<script>
(function () {
  var state = {
    main_tab: 'profiles',
    profiles: [],
    profile_draft: null,
    drivers: [],
    collections: [],
    active_profile_name: '',
    active_collection_name: '',
    active_collection_item_id: '',
    collection_item_draft: 0,
    collection_name_draft: '',
    collection_item_name_draft: '',
    schema: { tables: [], columns: [], active_table: '' },
    sql: '',
    result_html: '',
    result_info: 'Select a profile and run SQL.'
  };
  var endpointFallbacks = {
    'profiles.bootstrap': '/ajax/sql-dashboard-profiles-bootstrap?type=json',
    'profiles.save': '/ajax/sql-dashboard-profiles-save?type=json',
    'profiles.delete': '/ajax/sql-dashboard-profiles-delete?type=json',
    'collections.save': '/ajax/sql-dashboard-collections-save?type=json',
    'collections.delete': '/ajax/sql-dashboard-collections-delete?type=json',
    'sql.execute': '/ajax/sql-dashboard-execute?type=json',
    'schema.browse': '/ajax/sql-dashboard-schema-browse?type=json'
  };

  function byId(id) {
    return document.getElementById(id);
  }

  function banner(message, isError) {
    var node = byId('sql-banner');
    if (!message) {
      node.hidden = true;
      node.textContent = '';
      node.className = 'sql-banner';
      return;
    }
    node.hidden = false;
    node.className = 'sql-banner' + (isError ? ' is-error' : '');
    node.textContent = message;
  }

  function endpoint(path) {
    var root = (typeof window.configs !== 'undefined' && window.configs) ? window.configs : {};
    var node = root;
    path.split('.').forEach(function (part) {
      node = node && typeof node === 'object' ? node[part] : undefined;
    });
    return node || endpointFallbacks[path] || '';
  }

  function escapeHtml(value) {
    return String(value == null ? '' : value)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }

  function connectionIdFromParts(dsn, user) {
    dsn = String(dsn || '').trim();
    user = String(user || '').trim();
    if (!dsn || !user) return '';
    return dsn + '|' + user;
  }

  function parseConnectionId(connectionId) {
    connectionId = String(connectionId || '');
    if (!connectionId) return null;
    var index = connectionId.lastIndexOf('|');
    if (index < 1) return null;
    var dsn = connectionId.slice(0, index);
    var user = connectionId.slice(index + 1);
    if (!dsn || !user) return null;
    var driver = '';
    var match = dsn.match(/^dbi:([^:]+):/i);
    if (match && match[1]) driver = 'DBD::' + match[1];
    return {
      connection_id: connectionId,
      dsn: dsn,
      user: user,
      driver: driver,
      name: '',
      attrs_json: '{"RaiseError":1,"PrintError":0,"AutoCommit":1}',
      save_password: 0
    };
  }

  function findProfile(name) {
    return (state.profiles || []).filter(function (profile) {
      return profile && profile.name === name;
    })[0] || null;
  }

  function findProfileByConnection(connectionId) {
    return (state.profiles || []).filter(function (profile) {
      if (!profile) return false;
      return String(profile.connection_id || connectionIdFromParts(profile.dsn, profile.user)) === String(connectionId || '');
    })[0] || null;
  }

  function findCollection(name) {
    return (state.collections || []).filter(function (collection) {
      return collection && collection.name === name;
    })[0] || null;
  }

  function currentProfile() {
    return findProfile(state.active_profile_name) || state.profile_draft || null;
  }

  function currentCollection() {
    return findCollection(state.active_collection_name);
  }

  function currentCollectionItem() {
    var collection = currentCollection();
    if (!collection || !collection.items) return null;
    return (collection.items || []).filter(function (item) {
      return item && item.id === state.active_collection_item_id;
    })[0] || null;
  }

  function collectionItemIdFromName(name) {
    name = String(name || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
    return name || 'sql-item';
  }

  function currentFormConnectionId() {
    return connectionIdFromParts(byId('sql-profile-dsn').value, byId('sql-profile-user').value);
  }

  function currentRoute() {
    return {
      tab: state.main_tab || '',
      connection: currentFormConnectionId() || (currentProfile() && currentProfile().connection_id) || '',
      collection: state.active_collection_name || '',
      item: state.active_collection_item_id || '',
      table: state.schema.active_table || '',
      sql: byId('sql-editor').value || ''
    };
  }

  function routeQuery(route) {
    var params = new window.URLSearchParams();
    if (route.tab) params.set('tab', route.tab);
    if (route.connection) params.set('connection', route.connection);
    if (route.collection) params.set('collection', route.collection);
    if (route.item) params.set('item', route.item);
    if (route.table) params.set('table', route.table);
    if (route.sql) params.set('sql', route.sql);
    return params.toString();
  }

  function readRoute() {
    var params = new window.URLSearchParams(window.location.search || '');
    return {
      tab: params.get('tab') || '',
      connection: params.get('connection') || '',
      collection: params.get('collection') || '',
      item: params.get('item') || '',
      table: params.get('table') || '',
      sql: params.get('sql') || ''
    };
  }

  function syncRoute(replaceRoute) {
    if (!window.history || !window.URLSearchParams) return;
    var query = routeQuery(currentRoute());
    var target = window.location.pathname + (query ? '?' + query : '');
    var current = window.location.pathname + window.location.search;
    if (target === current) return;
    if (replaceRoute) {
      window.history.replaceState(currentRoute(), '', target);
      return;
    }
    window.history.pushState(currentRoute(), '', target);
  }

  function renderDriverOptions(selectedDriver) {
    var select = byId('sql-profile-driver');
    var selected = String(selectedDriver || select.value || '');
    var drivers = (state.drivers || []).slice();
    if (selected && drivers.indexOf(selected) === -1) {
      drivers.push(selected);
    }
    drivers.sort(function (left, right) {
      return String(left || '').localeCompare(String(right || ''));
    });
    select.innerHTML = '';
    var blank = document.createElement('option');
    blank.value = '';
    blank.textContent = drivers.length ? 'Select installed driver' : 'Install a DBD::* driver first';
    select.appendChild(blank);
    drivers.forEach(function (driver) {
      var option = document.createElement('option');
      option.value = driver;
      option.textContent = driver;
      if (driver === selected) option.selected = true;
      select.appendChild(option);
    });
    if (selected) select.value = selected;
  }

  function loadProfileIntoForm(profile) {
    profile = profile || {};
    renderDriverOptions(profile.driver || '');
    byId('sql-profile-name').value = profile.name || '';
    byId('sql-profile-dsn').value = profile.dsn || '';
    byId('sql-profile-user').value = profile.user || '';
    byId('sql-profile-password').value = profile.password || '';
    byId('sql-profile-save-password').checked = profile.save_password ? true : false;
    byId('sql-profile-attrs').value = profile.attrs_json || '{"RaiseError":1,"PrintError":0,"AutoCommit":1}';
  }

  function loadCollectionIntoForm(collection, item) {
    collection = collection || {};
    item = item || null;
    state.collection_name_draft = collection.name || state.collection_name_draft || '';
    state.collection_item_name_draft = item && item.name ? item.name : (state.collection_item_name_draft || '');
    byId('sql-collection-name').value = state.collection_name_draft;
    byId('sql-collection-item-name').value = state.collection_item_name_draft;
  }

  function autoResizeSqlEditor() {
    var editor = byId('sql-editor');
    if (!editor) return;
    editor.style.height = 'auto';
    var lines = String(editor.value || '').split(/\r?\n/).length;
    var lineBasedHeight = 120 + (lines * 28);
    var height = Math.max(editor.scrollHeight, lineBasedHeight, 240);
    editor.style.height = height + 'px';
  }

  function profilePayloadFromForm() {
    var dsn = byId('sql-profile-dsn').value.trim();
    var user = byId('sql-profile-user').value.trim();
    return {
      name: byId('sql-profile-name').value.trim(),
      driver: byId('sql-profile-driver').value.trim(),
      dsn: dsn,
      user: user,
      password: byId('sql-profile-password').value,
      save_password: byId('sql-profile-save-password').checked ? 1 : 0,
      attrs_json: byId('sql-profile-attrs').value.trim(),
      connection_id: connectionIdFromParts(dsn, user)
    };
  }

  function collectionPayloadFromState(collection) {
    return {
      name: String(collection && collection.name || '').trim(),
      items: (collection && collection.items || []).map(function (item) {
        return {
          id: String(item && item.id || '').trim(),
          name: String(item && item.name || '').trim(),
          sql: item && item.sql ? String(item.sql) : ''
        };
      })
    };
  }

  function upsertCollection(collection) {
    var existingIndex = -1;
    state.collections.forEach(function (item, index) {
      if (item && item.name === collection.name) existingIndex = index;
    });
    if (existingIndex > -1) {
      state.collections.splice(existingIndex, 1, collection);
    } else {
      state.collections.push(collection);
    }
    state.collections.sort(function (left, right) {
      return String(left.name || '').localeCompare(String(right.name || ''));
    });
  }

  function saveCollectionToServer(collection, originalName) {
    return requestJson(endpoint('collections.save'), 'POST', {
      collection: JSON.stringify(collectionPayloadFromState(collection)),
      original_name: originalName || ''
    }).then(function (payload) {
      if (!payload || !payload.ok) throw new Error((payload && payload.error) || 'Unable to save SQL collection');
      upsertCollection(payload.collection);
      state.active_collection_name = payload.collection.name || '';
      return payload.collection;
    });
  }

  function driverShortName(driver) {
    return String(driver || '').replace(/^DBD::/, '');
  }

  function updateDsnDriverPrefix() {
    var driver = byId('sql-profile-driver').value.trim();
    if (!driver) return;
    var shortName = driverShortName(driver);
    var dsnField = byId('sql-profile-dsn');
    var dsn = dsnField.value.trim();
    if (!dsn) {
      dsnField.value = 'dbi:' + shortName + ':';
      return;
    }
    if (/^dbi:[^:]+:/i.test(dsn)) {
      dsnField.value = dsn.replace(/^dbi:[^:]+:/i, 'dbi:' + shortName + ':');
      return;
    }
    dsnField.value = 'dbi:' + shortName + ':' + dsn;
  }

  function applyRoute(route, options) {
    options = options || {};
    if (route.tab) {
      state.main_tab = route.tab === 'collections' ? 'workspace' : route.tab;
    }
    if (route.connection) {
      var matchedProfile = findProfileByConnection(route.connection);
      if (matchedProfile) {
        state.active_profile_name = matchedProfile.name || '';
        state.profile_draft = null;
        loadProfileIntoForm(matchedProfile);
      } else {
        state.active_profile_name = '';
        state.profile_draft = parseConnectionId(route.connection);
        if (state.profile_draft) {
          loadProfileIntoForm(state.profile_draft);
        }
      }
    } else if (!state.active_profile_name && !state.profile_draft && state.profiles.length) {
      state.active_profile_name = state.profiles[0].name || '';
      loadProfileIntoForm(state.profiles[0]);
    }
    if (route.collection && findCollection(route.collection)) {
      state.active_collection_name = route.collection;
    }
    if (route.item) {
      state.active_collection_item_id = route.item;
      state.collection_item_draft = 0;
    }
    if (route.sql) {
      state.sql = route.sql;
    }
    if (route.table) {
      state.schema.active_table = route.table;
    }
    render(options);
  }

  function render(options) {
    options = options || {};
    var activeProfile = currentProfile();
    if (!state.active_collection_name && state.collections.length) {
      state.active_collection_name = state.collections[0].name || '';
    }
    var activeCollection = currentCollection();
    var activeCollectionItem = currentCollectionItem();
    if (activeCollection && activeCollection.items && activeCollection.items.length && !activeCollectionItem && state.active_collection_item_id) {
      state.active_collection_item_id = '';
    }
    if (activeCollection && activeCollection.items && activeCollection.items.length && !activeCollectionItem && !state.active_collection_item_id && !state.collection_item_draft) {
      state.active_collection_item_id = activeCollection.items[0].id || '';
      activeCollectionItem = currentCollectionItem();
    }
    byId('sql-editor').value = state.sql || byId('sql-editor').value || '';
    autoResizeSqlEditor();
    byId('sql-active-profile').textContent = activeProfile ? ('Active Connection: ' + (activeProfile.name || activeProfile.connection_id || currentFormConnectionId())) : 'No active profile selected';
    byId('sql-active-collection').textContent = activeCollection ? ('Collection: ' + activeCollection.name) : (state.collection_name_draft && state.collection_name_draft.trim() ? ('Collection: ' + state.collection_name_draft.trim()) : 'No active collection');
    byId('sql-active-sql-name').textContent = activeCollectionItem ? ('Saved SQL: ' + activeCollectionItem.name) : (state.collection_item_name_draft && state.collection_item_name_draft.trim() ? ('Unsaved SQL: ' + state.collection_item_name_draft.trim()) : 'Unsaved SQL');
    byId('sql-schema-profile').textContent = activeProfile ? ('Schema Explorer: ' + (activeProfile.name || activeProfile.connection_id || currentFormConnectionId())) : 'Schema Explorer';
    byId('sql-result-info').textContent = state.result_info || 'Select a profile and run SQL.';
    byId('sql-result-html').innerHTML = state.result_html || '';

    Array.prototype.forEach.call(document.querySelectorAll('.sql-main-tab'), function (tab) {
      var active = tab.getAttribute('data-sql-main-tab') === state.main_tab;
      tab.classList.toggle('is-active', active);
      tab.setAttribute('aria-selected', active ? 'true' : 'false');
    });
    Array.prototype.forEach.call(document.querySelectorAll('.sql-panel'), function (panel) {
      panel.classList.toggle('is-active', panel.id === 'sql-panel-' + state.main_tab);
    });

    var profileTabs = byId('sql-profile-tabs');
    profileTabs.innerHTML = '';
    (state.profiles || []).forEach(function (profile) {
      var tab = document.createElement('a');
      tab.href = '#';
      tab.className = 'sql-subtab' + (profile.name === state.active_profile_name ? ' is-active' : '');
      tab.textContent = profile.name;
      tab.setAttribute('data-sql-profile-tab', profile.name);
      tab.addEventListener('click', function (event) {
        event.preventDefault();
        state.active_profile_name = profile.name;
        state.profile_draft = null;
        loadProfileIntoForm(profile);
        render({ replaceRoute: false });
      });
      profileTabs.appendChild(tab);
    });
    byId('sql-profile-tabs-empty').style.display = (state.profiles || []).length ? 'none' : 'block';
    if (activeProfile) {
      loadProfileIntoForm(activeProfile);
    }

    var collectionTabs = byId('sql-collection-tabs');
    collectionTabs.innerHTML = '';
    (state.collections || []).forEach(function (collection) {
      var tab = document.createElement('a');
      tab.href = '#';
      tab.className = 'sql-subtab' + (collection.name === state.active_collection_name ? ' is-active' : '');
      tab.textContent = collection.name;
      tab.setAttribute('data-sql-collection-tab', collection.name);
      tab.addEventListener('click', function (event) {
        event.preventDefault();
        state.active_collection_name = collection.name;
        state.active_collection_item_id = ((collection.items || [])[0] || {}).id || '';
        state.collection_item_draft = 0;
        state.collection_item_name_draft = '';
        loadCollectionIntoForm(collection, currentCollectionItem());
        state.main_tab = 'workspace';
        render({ replaceRoute: false });
      });
      collectionTabs.appendChild(tab);
    });
    byId('sql-collection-tabs-empty').style.display = (state.collections || []).length ? 'none' : 'block';
    var collectionItemList = byId('sql-collection-item-list');
    collectionItemList.innerHTML = '';
    byId('sql-collection-item-list-title').textContent = activeCollection ? ('Saved SQL in ' + activeCollection.name) : 'Saved SQL';
    (activeCollection && activeCollection.items || []).forEach(function (item) {
      var row = document.createElement('div');
      row.className = 'sql-list-row' + (item.id === state.active_collection_item_id ? ' is-active' : '');
      var link = document.createElement('a');
      var preview = String(item.sql || '').split(/\r?\n/)[0] || 'Saved SQL';
      link.href = '#';
      link.className = 'sql-list-item' + (item.id === state.active_collection_item_id ? ' is-active' : '');
      link.innerHTML = '<span>' + escapeHtml(item.name) + '</span><small>' + escapeHtml(preview) + '</small>';
      link.setAttribute('data-sql-collection-item-link', item.id);
      link.setAttribute('data-sql-collection-item-tab', item.id);
      link.addEventListener('click', function (event) {
        event.preventDefault();
        state.active_collection_item_id = item.id;
        state.collection_item_draft = 0;
        state.sql = item.sql || '';
        loadCollectionIntoForm(activeCollection, item);
        state.main_tab = 'workspace';
        render({ replaceRoute: false });
      });
      row.appendChild(link);
      var remove = document.createElement('a');
      remove.href = '#';
      remove.className = 'sql-inline-delete';
      remove.textContent = '[X]';
      remove.setAttribute('data-sql-collection-item-delete', item.id);
      remove.setAttribute('aria-label', 'Delete ' + item.name);
      remove.addEventListener('click', function (event) {
        event.preventDefault();
        event.stopPropagation();
        var selectedCollection = currentCollection();
        if (!selectedCollection) {
          banner('Select a collection before deleting SQL.', true);
          return;
        }
        var collectionPayload = collectionPayloadFromState(selectedCollection);
        collectionPayload.items = (collectionPayload.items || []).filter(function (entry) {
          return entry && entry.id !== item.id;
        });
        var nextItem = (collectionPayload.items || [])[0] || null;
        saveCollectionToServer(collectionPayload, collectionPayload.name).then(function (savedCollection) {
          state.active_collection_name = savedCollection.name;
          state.active_collection_item_id = nextItem && nextItem.id ? nextItem.id : '';
          state.collection_item_draft = state.active_collection_item_id ? 0 : 1;
          state.collection_name_draft = savedCollection.name || '';
          state.collection_item_name_draft = nextItem && nextItem.name ? nextItem.name : '';
          state.sql = nextItem && nextItem.sql ? nextItem.sql : '';
          loadCollectionIntoForm(savedCollection, nextItem);
          banner('SQL removed from collection: ' + savedCollection.name, false);
          render({ replaceRoute: false });
        }).catch(function (error) {
          banner('SQL delete failed: ' + error.message, true);
        });
      });
      row.appendChild(remove);
      collectionItemList.appendChild(row);
    });
    byId('sql-collection-item-tabs-empty').style.display = activeCollection && activeCollection.items && activeCollection.items.length ? 'none' : 'block';
    loadCollectionIntoForm(activeCollection || {}, activeCollectionItem);

    var tableTabs = byId('sql-table-tabs');
    tableTabs.innerHTML = '';
    (state.schema.tables || []).forEach(function (row) {
      var tableName = row.TABLE_NAME || row.table_name || '';
      if (!tableName) return;
      var tab = document.createElement('a');
      tab.href = '#';
      tab.className = 'sql-subtab' + (tableName === state.schema.active_table ? ' is-active' : '');
      tab.textContent = tableName;
      tab.setAttribute('data-sql-table-tab', tableName);
      tab.addEventListener('click', function (event) {
        event.preventDefault();
        state.schema.active_table = tableName;
        state.main_tab = 'schema';
        render({ replaceRoute: false });
        refreshSchema(tableName);
      });
      tableTabs.appendChild(tab);
    });
    byId('sql-table-tabs-empty').style.display = (state.schema.tables || []).length ? 'none' : 'block';
    renderColumns();
    syncRoute(!!options.replaceRoute);
  }

  function renderColumns() {
    var activeTable = state.schema.active_table;
    var columns = (state.schema.columns || []).filter(function (row) {
      return !activeTable || (row.TABLE_NAME || activeTable) === activeTable;
    });
    if (!columns.length) {
      byId('sql-column-list').className = 'sql-empty';
      byId('sql-column-list').innerHTML = activeTable ? ('No column metadata returned for ' + escapeHtml(activeTable) + '.') : 'Select a table tab to view columns.';
      return;
    }
    var html = '<table><thead><tr><th>Column</th><th>Type</th><th>Length</th></tr></thead><tbody>';
    columns.forEach(function (row) {
      html += '<tr><td><pre>' + escapeHtml(row.COLUMN_NAME || '') + '</pre></td><td><pre>' + escapeHtml(row.DATA_TYPE || '') + '</pre></td><td><pre>' + escapeHtml(row.DATA_LENGTH || '') + '</pre></td></tr>';
    });
    html += '</tbody></table>';
    byId('sql-column-list').className = '';
    byId('sql-column-list').innerHTML = html;
  }

  function requestJson(url, method, body) {
    method = method || 'GET';
    var options = { method: method, headers: {} };
    if (body) {
      var form = new window.URLSearchParams();
      Object.keys(body).forEach(function (key) {
        form.append(key, body[key]);
      });
      options.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
      options.body = form.toString();
    }
    return window.fetch(url, options).then(function (response) {
      return response.text().then(function (text) {
        var payload;
        try {
          payload = text ? JSON.parse(text) : {};
        } catch (error) {
          throw new Error(text || ('Invalid JSON response from ' + url));
        }
        if (!response.ok) {
          throw new Error((payload && payload.error) || text || ('HTTP ' + response.status));
        }
        return payload;
      });
    });
  }

  function executeSql() {
    var profile = profilePayloadFromForm();
    profile.sql = byId('sql-editor').value;
    state.sql = profile.sql;
    return requestJson(endpoint('sql.execute'), 'POST', {
      settings: JSON.stringify(profile)
    }).then(function (payload) {
      if (!payload.ok) throw new Error(payload.error || 'Unable to run SQL');
      state.result_html = payload.html || '';
      state.result_info = JSON.stringify(payload.details || {}, null, 2);
      state.main_tab = 'workspace';
      banner('SQL executed successfully.', false);
      render({ replaceRoute: false });
      return payload;
    }).catch(function (error) {
      state.result_html = '';
      state.result_info = 'Execution failed.\n\n' + error.message;
      state.main_tab = 'workspace';
      banner('SQL execution failed: ' + error.message, true);
      render({ replaceRoute: false });
      throw error;
    });
  }

  function maybeAutoRunSharedRoute(route) {
    if (!route || !route.connection || !route.sql) return;
    var payload = profilePayloadFromForm();
    if (!payload.password) {
      banner('Shared SQL loaded. Add the local password for this connection profile before running it here.', false);
      render({ replaceRoute: true });
      return;
    }
    executeSql().catch(function () {});
  }

  function refreshSchema(targetTable) {
    var profile = profilePayloadFromForm();
    if (targetTable) {
      state.schema.active_table = targetTable;
    }
    profile.table_name = targetTable || state.schema.active_table || '';
    requestJson(endpoint('schema.browse'), 'POST', {
      settings: JSON.stringify(profile)
    }).then(function (payload) {
      if (!payload.ok) throw new Error(payload.error || 'Unable to load schema');
      state.schema.tables = payload.tables || [];
      state.schema.columns = payload.columns || [];
      if (payload.selected_table) {
        state.schema.active_table = payload.selected_table;
      }
      if (!state.schema.active_table && state.schema.tables.length) {
        state.schema.active_table = state.schema.tables[0].TABLE_NAME || '';
      }
      state.main_tab = 'schema';
      banner('Schema refreshed.', false);
      render();
    }).catch(function (error) {
      banner('Schema refresh failed: ' + error.message, true);
    });
  }

  function bootstrapProfiles() {
    return requestJson(endpoint('profiles.bootstrap'), 'GET').then(function (payload) {
      if (!payload || !payload.ok) {
        banner((payload && payload.error) || 'Unable to load SQL connection profiles.', true);
        return;
      }
      state.profiles = payload.profiles || [];
      state.collections = payload.collections || [];
      state.drivers = payload.drivers || [];
      if (!state.active_profile_name && state.profiles.length) {
        state.active_profile_name = state.profiles[0].name || '';
      }
      if (!state.active_collection_name && state.collections.length) {
        state.active_collection_name = state.collections[0].name || '';
        state.active_collection_item_id = (((state.collections[0] || {}).items || [])[0] || {}).id || '';
      }
      renderDriverOptions((state.profile_draft && state.profile_draft.driver) || '');
      applyRoute(readRoute(), { replaceRoute: true });
      banner(payload.errors && payload.errors.length ? payload.errors.join(' ') : '', (payload.errors || []).length > 0);
      maybeAutoRunSharedRoute(readRoute());
    }).catch(function (error) {
      banner('Unable to load SQL connection profiles: ' + error.message, true);
    });
  }

  byId('sql-profile-new').addEventListener('click', function (event) {
    event.preventDefault();
    state.active_profile_name = '';
    state.profile_draft = {
      attrs_json: '{"RaiseError":1,"PrintError":0,"AutoCommit":1}',
      save_password: 0
    };
    loadProfileIntoForm(state.profile_draft);
    state.main_tab = 'profiles';
    render({ replaceRoute: false });
  });

  byId('sql-profile-save').addEventListener('click', function (event) {
    event.preventDefault();
    var profile = profilePayloadFromForm();
    var originalName = state.active_profile_name || '';
    requestJson(endpoint('profiles.save'), 'POST', {
      profile: JSON.stringify(profile),
      original_name: originalName
    }).then(function (payload) {
      if (!payload.ok) throw new Error(payload.error || 'Unable to save profile');
      state.profiles = (state.profiles || []).filter(function (item) {
        return item && item.name !== originalName;
      });
      state.profiles.push(payload.profile);
      state.profiles.sort(function (left, right) {
        return String(left.name || '').localeCompare(String(right.name || ''));
      });
      state.active_profile_name = payload.profile.name;
      state.profile_draft = null;
      banner('Profile saved: ' + payload.profile.name, false);
      render({ replaceRoute: false });
    }).catch(function (error) {
      banner('Profile save failed: ' + error.message, true);
    });
  });

  byId('sql-profile-delete').addEventListener('click', function (event) {
    event.preventDefault();
    if (!state.active_profile_name) {
      banner('Select a profile to delete.', true);
      return;
    }
    requestJson(endpoint('profiles.delete'), 'POST', {
      name: state.active_profile_name
    }).then(function (payload) {
      if (!payload.ok) throw new Error(payload.error || 'Unable to delete profile');
      state.profiles = (state.profiles || []).filter(function (profile) {
        return profile && profile.name !== state.active_profile_name;
      });
      state.active_profile_name = '';
      state.profile_draft = null;
      banner('Profile deleted: ' + payload.removed, false);
      render({ replaceRoute: false });
    }).catch(function (error) {
      banner('Profile delete failed: ' + error.message, true);
    });
  });

  byId('sql-collection-new').addEventListener('click', function (event) {
    event.preventDefault();
    state.active_collection_name = '';
    state.active_collection_item_id = '';
    state.collection_item_draft = 1;
    state.collection_name_draft = '';
    state.collection_item_name_draft = '';
    loadCollectionIntoForm({}, null);
    state.main_tab = 'workspace';
    render({ replaceRoute: false });
  });

  byId('sql-collection-save').addEventListener('click', function (event) {
    event.preventDefault();
    var originalName = state.active_collection_name || '';
    var collection = currentCollection() || { items: [] };
    collection = collectionPayloadFromState(collection);
    collection.name = byId('sql-collection-name').value.trim();
    if (!collection.name) {
      banner('Collection name is required.', true);
      return;
    }
    state.collection_name_draft = collection.name;
    saveCollectionToServer(collection, originalName).then(function (savedCollection) {
      if (originalName && originalName !== savedCollection.name) {
        state.collections = state.collections.filter(function (item) {
          return item && item.name !== originalName;
        });
        upsertCollection(savedCollection);
      }
      if (!state.active_collection_item_id && savedCollection.items && savedCollection.items.length && !state.collection_item_draft) {
        state.active_collection_item_id = savedCollection.items[0].id || '';
      }
      state.collection_name_draft = savedCollection.name || '';
      state.main_tab = 'workspace';
      banner('Collection saved: ' + savedCollection.name, false);
      render({ replaceRoute: false });
    }).catch(function (error) {
      banner('Collection save failed: ' + error.message, true);
    });
  });

  byId('sql-collection-delete').addEventListener('click', function (event) {
    event.preventDefault();
    var name = state.active_collection_name || byId('sql-collection-name').value.trim();
    if (!name) {
      banner('Select a collection to delete.', true);
      return;
    }
    requestJson(endpoint('collections.delete'), 'POST', {
      name: name
    }).then(function (payload) {
      if (!payload || !payload.ok) throw new Error((payload && payload.error) || 'Unable to delete SQL collection');
      state.collections = (state.collections || []).filter(function (collection) {
        return collection && collection.name !== name;
      });
      state.active_collection_name = state.collections.length ? state.collections[0].name : '';
      state.active_collection_item_id = (((state.collections[0] || {}).items || [])[0] || {}).id || '';
      state.collection_item_draft = 0;
      state.collection_name_draft = state.active_collection_name || '';
      state.collection_item_name_draft = '';
      loadCollectionIntoForm(currentCollection() || {}, currentCollectionItem());
      banner('Collection deleted: ' + payload.removed, false);
      render({ replaceRoute: false });
    }).catch(function (error) {
      banner('Collection delete failed: ' + error.message, true);
    });
  });

  byId('sql-collection-item-new').addEventListener('click', function (event) {
    event.preventDefault();
    state.active_collection_item_id = '';
    state.collection_item_draft = 1;
    state.collection_item_name_draft = '';
    byId('sql-collection-item-name').value = '';
    byId('sql-active-sql-name').textContent = 'Unsaved SQL';
    state.main_tab = 'workspace';
    render({ replaceRoute: false });
    byId('sql-collection-item-name').focus();
  });

  byId('sql-collection-item-save').addEventListener('click', function (event) {
    event.preventDefault();
    var collectionName = byId('sql-collection-name').value.trim() || state.active_collection_name || '';
    var itemName = byId('sql-collection-item-name').value.trim();
    var sql = byId('sql-editor').value;
    if (!collectionName) {
      banner('Collection name is required before saving SQL.', true);
      return;
    }
    if (!itemName) {
      banner('SQL name is required before saving SQL.', true);
      return;
    }
    if (!sql.trim()) {
      banner('SQL text is required before saving SQL.', true);
      return;
    }
    state.collection_name_draft = collectionName;
    state.collection_item_name_draft = itemName;
    var collection = currentCollection();
    if (!collection || collection.name !== collectionName) {
      collection = { name: collectionName, items: [] };
    }
    collection = collectionPayloadFromState(collection);
    collection.name = collectionName;
    var requestedItemId = collectionItemIdFromName(itemName);
    var activeItem = currentCollectionItem();
    var itemId = requestedItemId;
    if (!state.collection_item_draft && activeItem && activeItem.name === itemName) {
      itemId = activeItem.id;
    }
    var replaced = false;
    collection.items = (collection.items || []).map(function (item) {
      if (item && item.id === itemId) {
        replaced = true;
        return { id: itemId, name: itemName, sql: sql };
      }
      return item;
    });
    if (!replaced) collection.items.push({ id: itemId, name: itemName, sql: sql });
    collection.items.sort(function (left, right) {
      return String(left.name || '').localeCompare(String(right.name || ''));
    });
    saveCollectionToServer(collection, state.active_collection_name || '').then(function (savedCollection) {
      state.active_collection_name = savedCollection.name;
      state.active_collection_item_id = itemId;
      state.collection_item_draft = 0;
      state.collection_name_draft = savedCollection.name || '';
      state.collection_item_name_draft = itemName;
      state.sql = sql;
      state.main_tab = 'workspace';
      banner('SQL saved to collection: ' + savedCollection.name, false);
      render({ replaceRoute: false });
    }).catch(function (error) {
      banner('SQL collection save failed: ' + error.message, true);
    });
  });

  byId('sql-run').addEventListener('click', function (event) {
    event.preventDefault();
    executeSql().catch(function () {});
  });

  byId('sql-schema-refresh').addEventListener('click', function (event) {
    event.preventDefault();
    refreshSchema();
  });

  Array.prototype.forEach.call(document.querySelectorAll('.sql-main-tab'), function (tab) {
    tab.addEventListener('click', function (event) {
      event.preventDefault();
      state.main_tab = tab.getAttribute('data-sql-main-tab') || 'profiles';
      render({ replaceRoute: false });
      if (state.main_tab === 'schema') {
        refreshSchema();
      }
    });
  });

  byId('sql-profile-driver').addEventListener('change', function () {
    updateDsnDriverPrefix();
    syncRoute(true);
  });

  byId('sql-editor').addEventListener('input', function () {
    state.sql = byId('sql-editor').value;
    autoResizeSqlEditor();
    byId('sql-editor-note').textContent = 'Edit SQL above, then use save to keep it in a collection or run to execute it with the active connection.';
    syncRoute(true);
  });

  byId('sql-editor').addEventListener('blur', function () {
    autoResizeSqlEditor();
    byId('sql-editor-note').textContent = 'Edit SQL above, then use save to keep it in a collection or run to execute it with the active connection.';
  });

  byId('sql-collection-name').addEventListener('input', function () {
    state.collection_name_draft = byId('sql-collection-name').value;
  });

  byId('sql-collection-item-name').addEventListener('input', function () {
    state.collection_item_name_draft = byId('sql-collection-item-name').value;
    if (!state.active_collection_item_id || state.collection_item_draft) {
      byId('sql-active-sql-name').textContent = state.collection_item_name_draft.trim() ? ('Unsaved SQL: ' + state.collection_item_name_draft.trim()) : 'Unsaved SQL';
    }
  });

  byId('sql-profile-dsn').addEventListener('input', function () {
    syncRoute(true);
  });

  byId('sql-profile-user').addEventListener('input', function () {
    syncRoute(true);
  });

  window.addEventListener('popstate', function (event) {
    applyRoute((event && event.state) || readRoute(), { replaceRoute: true });
  });

  loadProfileIntoForm({
    attrs_json: '{"RaiseError":1,"PrintError":0,"AutoCommit":1}',
    save_password: 0
  });
  renderDriverOptions('');
  bootstrapProfiles();
}());
</script>
HTML
}

# _sql_dashboard_profile_helpers()
# Builds shared Perl helpers used by the saved sql-dashboard profile endpoints.
# Input: none.
# Output: Perl source string.
sub _sql_dashboard_profile_helpers {
    return <<'PERL';
use Developer::Dashboard::Folder ();
use Developer::Dashboard::JSON qw(json_encode json_decode);
use File::Path qw(make_path);
use File::Spec;

sub sql_dashboard_trim {
    my ($text) = @_;
    $text = '' if !defined $text;
    $text =~ s/^\s+//;
    $text =~ s/\s+\z//;
    return $text;
}

sub sql_dashboard_escape_html {
    my ($text) = @_;
    $text = '' if !defined $text;
    $text =~ s/&/&amp;/g;
    $text =~ s/</&lt;/g;
    $text =~ s/>/&gt;/g;
    $text =~ s/"/&quot;/g;
    return $text;
}

# sql_dashboard_connection_id($dsn, $user)
# Purpose: build the shareable sql-dashboard connection id from DSN and user without exposing the password.
# Input: DSN string and database username string.
# Output: connection id string in the form "dsn|user".
sub sql_dashboard_connection_id {
    my ( $dsn, $user ) = @_;
    $dsn  = sql_dashboard_trim($dsn);
    $user = sql_dashboard_trim($user);
    return '' if $dsn eq '' || $user eq '';
    return $dsn . '|' . $user;
}

# sql_dashboard_parse_connection_id($connection_id)
# Purpose: recover DSN, user, and inferred driver details from one shareable sql-dashboard connection id.
# Input: connection id string in the form "dsn|user".
# Output: hash reference with connection_id, dsn, user, and driver keys or undef for invalid input.
sub sql_dashboard_parse_connection_id {
    my ($connection_id) = @_;
    $connection_id = sql_dashboard_trim($connection_id);
    return undef if $connection_id eq '' || $connection_id !~ /\A(.+)\|([^|]+)\z/;
    my ( $dsn, $user ) = ( $1, $2 );
    my $driver = '';
    if ( $dsn =~ /\Adbi:([^:]+):/i ) {
        $driver = 'DBD::' . $1;
    }
    return {
        connection_id => $connection_id,
        dsn           => $dsn,
        user          => $user,
        driver        => $driver,
    };
}

# sql_dashboard_secure_dir($dir)
# Purpose: ensure the sql-dashboard profile directory exists and is owner-only.
# Input: absolute directory path for the sql-dashboard profile store.
# Output: the same directory path after it has been created/tightened to 0700.
sub sql_dashboard_secure_dir {
    my ($dir) = @_;
    make_path( $dir, { mode => 0700 } ) if !-d $dir;
    chmod 0700, $dir or die "Unable to chmod $dir to 0700: $!";
    return $dir;
}

# sql_dashboard_secure_file($path)
# Purpose: tighten one existing sql-dashboard profile file to owner-only mode.
# Input: absolute file path for a saved sql-dashboard profile json file.
# Output: the same file path after it has been tightened to 0600.
sub sql_dashboard_secure_file {
    my ($path) = @_;
    return $path if !-e $path;
    chmod 0600, $path or die "Unable to chmod $path to 0600: $!";
    return $path;
}

sub sql_dashboard_profile_dir {
    my $dir = File::Spec->catdir( Developer::Dashboard::Folder->configs, 'sql-dashboard' );
    return sql_dashboard_secure_dir($dir);
}

sub sql_dashboard_profile_filename {
    my ($name) = @_;
    $name = sql_dashboard_trim($name);
    die "Missing profile name\n" if $name eq '';
    die "Invalid profile name\n" if $name =~ /[[:cntrl:]]/;
    $name =~ s{[\\/]+}{_}g;
    return $name . '.json';
}

sub sql_dashboard_profile_path {
    my ($name) = @_;
    return File::Spec->catfile( sql_dashboard_profile_dir(), sql_dashboard_profile_filename($name) );
}

# sql_dashboard_collection_dir()
# Purpose: resolve and secure the sql-dashboard collection directory used for saved SQL collections.
# Input: none.
# Output: absolute collection directory path with 0700 permissions enforced.
sub sql_dashboard_collection_dir {
    my $dir = File::Spec->catdir( sql_dashboard_profile_dir(), 'collections' );
    return sql_dashboard_secure_dir($dir);
}

# sql_dashboard_collection_filename($name)
# Purpose: turn one sql-dashboard collection name into a safe JSON filename.
# Input: collection name string.
# Output: filename string ending in .json.
sub sql_dashboard_collection_filename {
    my ($name) = @_;
    $name = sql_dashboard_trim($name);
    die "Missing collection name\n" if $name eq '';
    die "Invalid collection name\n" if $name =~ /[[:cntrl:]]/;
    $name =~ s{[\\/]+}{_}g;
    return $name . '.json';
}

# sql_dashboard_collection_path($name)
# Purpose: resolve one sql-dashboard collection JSON path on disk.
# Input: collection name string.
# Output: absolute collection JSON file path.
sub sql_dashboard_collection_path {
    my ($name) = @_;
    return File::Spec->catfile( sql_dashboard_collection_dir(), sql_dashboard_collection_filename($name) );
}

# sql_dashboard_normalize_profile($profile)
# Purpose: enrich one sql-dashboard profile hash with the portable connection id used in share URLs.
# Input: profile hash reference.
# Output: normalized profile hash reference.
sub sql_dashboard_normalize_profile {
    my ($profile) = @_;
    die "Profile must be a hash reference\n" if ref($profile) ne 'HASH';
    $profile->{name}          = sql_dashboard_trim( $profile->{name} );
    $profile->{driver}        = sql_dashboard_trim( $profile->{driver} );
    $profile->{dsn}           = sql_dashboard_trim( $profile->{dsn} );
    $profile->{user}          = sql_dashboard_trim( $profile->{user} );
    $profile->{attrs_json}    = sql_dashboard_trim( $profile->{attrs_json} );
    $profile->{save_password} = $profile->{save_password} ? 1 : 0;
    $profile->{password}      = '' if !defined $profile->{password};
    $profile->{connection_id} = sql_dashboard_connection_id( $profile->{dsn}, $profile->{user} );
    return $profile;
}

sub sql_dashboard_decode_profile_payload {
    my ($json_text) = @_;
    die "Missing profile payload\n" if !defined $json_text || $json_text eq '';
    my $profile = json_decode($json_text);
    die "Profile payload must be a JSON object\n" if ref($profile) ne 'HASH';
    $profile->{name}          = sql_dashboard_trim( $profile->{name} );
    $profile->{driver}        = sql_dashboard_trim( $profile->{driver} );
    $profile->{dsn}           = sql_dashboard_trim( $profile->{dsn} );
    $profile->{user}          = sql_dashboard_trim( $profile->{user} );
    $profile->{attrs_json}    = sql_dashboard_trim( $profile->{attrs_json} );
    $profile->{password}      = defined $profile->{password} ? $profile->{password} : '';
    $profile->{save_password} = $profile->{save_password} ? 1 : 0;
    die "Missing profile name\n" if $profile->{name} eq '';
    die "Missing driver module\n" if $profile->{driver} eq '';
    die "Missing DSN\n" if $profile->{dsn} eq '';
    die "Missing database user\n" if $profile->{user} eq '';
    if ( $profile->{attrs_json} ne '' ) {
        my $attrs = json_decode( $profile->{attrs_json} );
        die "DBI attrs_json must decode to a JSON object\n" if ref($attrs) ne 'HASH';
    }
    if ( !$profile->{save_password} ) {
        delete $profile->{password};
    }
    return sql_dashboard_normalize_profile($profile);
}

# sql_dashboard_decode_collection_payload($json_text)
# Purpose: validate and normalize one sql-dashboard SQL collection payload from the browser.
# Input: JSON text string containing collection metadata and SQL items.
# Output: normalized collection hash reference.
sub sql_dashboard_decode_collection_payload {
    my ($json_text) = @_;
    die "Missing collection payload\n" if !defined $json_text || $json_text eq '';
    my $collection = json_decode($json_text);
    die "Collection payload must be a JSON object\n" if ref($collection) ne 'HASH';
    $collection->{name} = sql_dashboard_trim( $collection->{name} );
    die "Missing collection name\n" if $collection->{name} eq '';
    die "Collection items must be an array\n" if ref( $collection->{items} ) ne 'ARRAY';
    my @items;
    for my $item ( @{ $collection->{items} } ) {
        die "Collection item must be a JSON object\n" if ref($item) ne 'HASH';
        my $name = sql_dashboard_trim( $item->{name} );
        my $id   = sql_dashboard_trim( $item->{id} );
        my $sql  = defined $item->{sql} ? $item->{sql} : '';
        die "Missing collection item name\n" if $name eq '';
        if ( $id eq '' ) {
            $id = lc($name);
            $id =~ s/[^a-z0-9]+/-/g;
            $id =~ s/^-+|-+\z//g;
            $id = 'sql-item' if $id eq '';
        }
        push @items, {
            id   => $id,
            name => $name,
            sql  => $sql,
        };
    }
    @items = sort { ( $a->{name} || '' ) cmp ( $b->{name} || '' ) } @items;
    $collection->{items} = \@items;
    return $collection;
}

sub sql_dashboard_read_profile {
    my ($name) = @_;
    my $path = sql_dashboard_profile_path($name);
    sql_dashboard_secure_file($path);
    open my $fh, '<', $path or die "Unable to read $path: $!";
    local $/;
    my $json_text = <$fh>;
    close $fh or die "Unable to close $path: $!";
    my $profile = json_decode($json_text);
    die "Invalid sql-dashboard profile file $path\n" if ref($profile) ne 'HASH';
    return sql_dashboard_normalize_profile($profile);
}

# sql_dashboard_read_collection($name)
# Purpose: read one saved sql-dashboard collection JSON file from disk.
# Input: collection name string.
# Output: normalized collection hash reference.
sub sql_dashboard_read_collection {
    my ($name) = @_;
    my $path = sql_dashboard_collection_path($name);
    sql_dashboard_secure_file($path);
    open my $fh, '<', $path or die "Unable to read $path: $!";
    local $/;
    my $json_text = <$fh>;
    close $fh or die "Unable to close $path: $!";
    return sql_dashboard_decode_collection_payload($json_text);
}

sub sql_dashboard_list_profiles {
    my @profiles;
    my @errors;
    my $dir = sql_dashboard_profile_dir();
    opendir my $dh, $dir or die "Unable to read $dir: $!";
    while ( my $entry = readdir $dh ) {
        next if $entry eq '.' || $entry eq '..';
        next if $entry !~ /\.json\z/;
        my $path = File::Spec->catfile( $dir, $entry );
        next if !-f $path;
        eval {
            sql_dashboard_secure_file($path);
            open my $fh, '<', $path or die "Unable to read $path: $!";
            local $/;
            my $json_text = <$fh>;
            close $fh or die "Unable to close $path: $!";
            my $profile = json_decode($json_text);
            die "Profile file $path did not decode to a JSON object\n" if ref($profile) ne 'HASH';
            push @profiles, sql_dashboard_normalize_profile($profile);
            1;
        } or do {
            my $error = $@ || "Unable to read $path\n";
            $error =~ s/\s+\z//;
            push @errors, $error;
        };
    }
    closedir $dh or die "Unable to close $dir: $!";
    @profiles = sort { ( $a->{name} || '' ) cmp ( $b->{name} || '' ) } @profiles;
    return ( \@profiles, \@errors );
}

# sql_dashboard_list_collections()
# Purpose: read every saved sql-dashboard collection from disk and surface any parse failures.
# Input: none.
# Output: array reference of normalized collections and array reference of read errors.
sub sql_dashboard_list_collections {
    my @collections;
    my @errors;
    my $dir = sql_dashboard_collection_dir();
    opendir my $dh, $dir or die "Unable to read $dir: $!";
    while ( my $entry = readdir $dh ) {
        next if $entry eq '.' || $entry eq '..';
        next if $entry !~ /\.json\z/;
        my $path = File::Spec->catfile( $dir, $entry );
        next if !-f $path;
        eval {
            sql_dashboard_secure_file($path);
            open my $fh, '<', $path or die "Unable to read $path: $!";
            local $/;
            my $json_text = <$fh>;
            close $fh or die "Unable to close $path: $!";
            push @collections, sql_dashboard_decode_collection_payload($json_text);
            1;
        } or do {
            my $error = $@ || "Unable to read $path\n";
            $error =~ s/\s+\z//;
            push @errors, $error;
        };
    }
    closedir $dh or die "Unable to close $dir: $!";
    @collections = sort { ( $a->{name} || '' ) cmp ( $b->{name} || '' ) } @collections;
    return ( \@collections, \@errors );
}

# sql_dashboard_list_drivers()
# Purpose: discover installed DBD::* modules visible to the sql-dashboard bookmark worker.
# Input: none.
# Output: array reference of sorted DBD::* module names.
sub sql_dashboard_list_drivers {
    my %drivers;
    for my $inc (@INC) {
        next if !defined $inc || ref($inc);
        my $dir = File::Spec->catdir( $inc, 'DBD' );
        next if !-d $dir;
        opendir my $dh, $dir or next;
        while ( my $entry = readdir $dh ) {
            next if $entry !~ /\A([A-Za-z_][A-Za-z0-9_]*)\.pm\z/;
            $drivers{ 'DBD::' . $1 } = 1;
        }
        closedir $dh;
    }
    return [ sort keys %drivers ];
}

# sql_dashboard_find_profile_by_connection_id($connection_id)
# Purpose: resolve one shareable sql-dashboard connection id back to a saved local profile when it exists.
# Input: connection id string.
# Output: normalized profile hash reference or undef.
sub sql_dashboard_find_profile_by_connection_id {
    my ($connection_id) = @_;
    $connection_id = sql_dashboard_trim($connection_id);
    return undef if $connection_id eq '';
    my ( $profiles, undef ) = sql_dashboard_list_profiles();
    for my $profile ( @{$profiles} ) {
        return $profile if ( $profile->{connection_id} || '' ) eq $connection_id;
    }
    return undef;
}
PERL
}

# _sql_dashboard_runtime_helpers()
# Builds the saved sql-dashboard helpers that resolve profiles into generic DBI connection settings and render result HTML safely.
# Input: none.
# Output: Perl source string.
sub _sql_dashboard_runtime_helpers {
    return _sql_dashboard_profile_helpers() . <<'PERL';
sub sql_dashboard_driver_module {
    my ($settings) = @_;
    my $driver = sql_dashboard_trim( $settings->{driver} );
    if ( $driver eq '' ) {
        my $dsn = sql_dashboard_trim( $settings->{dsn} );
        if ( $dsn =~ /\Adbi:([^:]+):/i ) {
            $driver = 'DBD::' . $1;
        }
    }
    die "Missing driver module\n" if $driver eq '';
    die "Invalid driver module '$driver'\n" if $driver !~ /\A[A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z0-9_]+)*\z/;
    return $driver;
}

sub sql_dashboard_resolve_settings {
    my ($settings) = @_;
    die "SQL settings payload must be a JSON object\n" if ref($settings) ne 'HASH';
    my %resolved = %{$settings};
    if ( sql_dashboard_trim( $resolved{profile_name} ) ne '' ) {
        my $profile = sql_dashboard_read_profile( $resolved{profile_name} );
        %resolved = ( %{$profile}, %resolved );
    }
    elsif ( sql_dashboard_trim( $resolved{connection_id} ) ne '' ) {
        my $profile = sql_dashboard_find_profile_by_connection_id( $resolved{connection_id} );
        if ($profile) {
            %resolved = ( %{$profile}, %resolved );
            $resolved{profile_name} = $profile->{name};
        }
        my $parsed = sql_dashboard_parse_connection_id( $resolved{connection_id} );
        if ($parsed) {
            $resolved{dsn}    = $parsed->{dsn}    if sql_dashboard_trim( $resolved{dsn} ) eq '';
            $resolved{user}   = $parsed->{user}   if sql_dashboard_trim( $resolved{user} ) eq '';
            $resolved{driver} = $parsed->{driver} if sql_dashboard_trim( $resolved{driver} ) eq '';
        }
    }
    $resolved{driver}     = sql_dashboard_driver_module( \%resolved );
    $resolved{dsn}        = sql_dashboard_trim( $resolved{dsn} );
    $resolved{user}       = sql_dashboard_trim( $resolved{user} );
    $resolved{password}   = defined $resolved{password} ? $resolved{password} : '';
    $resolved{attrs_json} = sql_dashboard_trim( $resolved{attrs_json} );
    $resolved{connection_id} = sql_dashboard_connection_id( $resolved{dsn}, $resolved{user} );
    die "Missing DSN\n" if $resolved{dsn} eq '';
    die "Missing database user\n" if $resolved{user} eq '';
    return \%resolved;
}

sub sql_dashboard_require_db_stack {
    my ($settings) = @_;
    eval { require DBI; 1 } or die "Missing DBI. Install it with dashboard cpan DBI\n";
    my $driver = $settings->{driver};
    eval "require $driver; 1" or die "Missing $driver. Install it with dashboard cpan $driver\n";
    return 1;
}

sub sql_dashboard_connect {
    my ($settings) = @_;
    sql_dashboard_require_db_stack($settings);
    my $attrs = {
        RaiseError => 1,
        PrintError => 0,
        AutoCommit => 1,
    };
    if ( $settings->{attrs_json} ne '' ) {
        my $decoded = json_decode( $settings->{attrs_json} );
        die "DBI attrs_json must decode to a JSON object\n" if ref($decoded) ne 'HASH';
        %{$attrs} = ( %{$attrs}, %{$decoded} );
    }
    my $dbh = DBI->connect( $settings->{dsn}, $settings->{user}, $settings->{password}, $attrs );
    die "Unable to connect to $settings->{dsn}\n" if !$dbh;
    return $dbh;
}

sub sql_dashboard_parse_sql_blocks {
    my ( $sql_text, $sqls_sep, $instruction_sep ) = @_;
    my @blocks;
    for my $raw_block ( split /\Q$sqls_sep\E/s, $sql_text ) {
        next if !defined $raw_block;
        $raw_block =~ s/^\s+//;
        $raw_block =~ s/\s+\z//;
        next if $raw_block eq '';
        my ( $sql, @raw_instructions ) = split /\Q$instruction_sep\E/s, $raw_block;
        $sql = '' if !defined $sql;
        $sql =~ s/^\s+//;
        $sql =~ s/\s+\z//;
        next if $sql eq '';
        my %instructions;
        for my $instruction (@raw_instructions) {
            next if !defined $instruction;
            $instruction =~ s/^\s+//;
            $instruction =~ s/\s+\z//;
            next if $instruction eq '';
            if ( $instruction =~ /\ASTASH:\s*(.+)\z/s ) {
                my $hash = eval "no strict; no warnings; { $1 }";
                die "STASH Error: $@\n" if $@;
                die "STASH instruction must return a hash reference\n" if ref($hash) ne 'HASH';
                $instructions{stash} ||= [];
                push @{ $instructions{stash} }, $hash;
            }
            elsif ( $instruction =~ /\AROW:\s*(.+)\z/s ) {
                my $code = "sub {\nmy (\$row, \$row_number_ref, \$columns, \$sth, \$dbh, \$stash) = \@_;\n$1\n}";
                my $sub = eval $code;
                die "ROW Error: $@\n" if $@;
                $instructions{row} = $sub;
            }
            elsif ( $instruction =~ /\ABEFORE:\s*(.+)\z/s ) {
                my $code = "sub {\nmy (\$columns, \$sth, \$dbh, \$stash) = \@_;\n$1\n}";
                my $sub = eval $code;
                die "BEFORE Error: $@\n" if $@;
                $instructions{before} = $sub;
            }
            elsif ( $instruction =~ /\AAFTER:\s*(.+)\z/s ) {
                my $code = "sub {\nmy (\$columns, \$dbh, \$stash) = \@_;\n$1\n}";
                my $sub = eval $code;
                die "AFTER Error: $@\n" if $@;
                $instructions{after} = $sub;
            }
            else {
                die "Unknown instruction block: $instruction\n";
            }
        }
        push @blocks, {
            sql          => $sql,
            instructions => \%instructions,
        };
    }
    return \@blocks;
}

sub sql_dashboard_render_table_html {
    my ( $columns, $rows, $statement_sql, $rows_affected ) = @_;
    my $html = '<table><thead>';
    $html .= '<tr><th colspan="' . ( scalar(@{$columns}) + 1 ) . '">SQL: <code>' . sql_dashboard_escape_html($statement_sql) . '</code></th></tr>';
    if ( !@{$columns} ) {
        $html .= '<tr><th>Rows affected: ' . sql_dashboard_escape_html($rows_affected) . '</th></tr></thead><tbody></tbody></table>';
        return $html;
    }
    $html .= '<tr><th>#</th>';
    for my $column ( @{$columns} ) {
        $html .= '<th>' . sql_dashboard_escape_html($column) . '</th>';
    }
    $html .= '</tr></thead><tbody>';
    for my $row_entry ( @{$rows} ) {
        my $style = $row_entry->{hl} ? ' style="' . sql_dashboard_escape_html( $row_entry->{hl} ) . '"' : '';
        $html .= '<tr' . $style . '><td><pre>' . sql_dashboard_escape_html( $row_entry->{row_number} ) . '</pre></td>';
        for my $column ( @{$columns} ) {
            my $value = $row_entry->{values}{$column};
            if ( ref($value) eq 'HASH' && exists $value->{html} ) {
                $html .= '<td>' . $value->{html} . '</td>';
            }
            else {
                $html .= '<td><pre>' . sql_dashboard_escape_html($value) . '</pre></td>';
            }
        }
        $html .= '</tr>';
    }
    $html .= '</tbody></table>';
    return $html;
}
PERL
}

# _sql_dashboard_profiles_bootstrap_code()
# Builds the saved sql-dashboard bootstrap Ajax handler.
# Input: none.
# Output: Perl source string.
sub _sql_dashboard_profiles_bootstrap_code {
    return _sql_dashboard_profile_helpers() . <<'PERL';
my $payload;
eval {
    my ( $profiles, $errors ) = sql_dashboard_list_profiles();
    my ( $collections, $collection_errors ) = sql_dashboard_list_collections();
    my $drivers = sql_dashboard_list_drivers();
    $payload = {
        ok          => 1,
        profiles    => $profiles,
        collections => $collections,
        drivers     => $drivers,
        errors      => [ @{$errors}, @{$collection_errors} ],
    };
    1;
} or do {
    my $error = $@ || 'Unknown sql-dashboard bootstrap failure';
    $error =~ s/\s+\z//;
    $payload = {
        ok          => 0,
        error       => $error,
        profiles    => [],
        collections => [],
        drivers     => [],
        errors      => [],
    };
};
print j $payload;
PERL
}

# _sql_dashboard_profiles_save_code()
# Builds the saved sql-dashboard profile-save Ajax handler.
# Input: none.
# Output: Perl source string.
sub _sql_dashboard_profiles_save_code {
    return _sql_dashboard_profile_helpers() . <<'PERL';
my $payload;
eval {
    my $profile = sql_dashboard_decode_profile_payload( params->{profile} );
    my $original_name = sql_dashboard_trim( params->{original_name} );
    my $path = sql_dashboard_profile_path( $profile->{name} );
    if ( $original_name ne '' && $original_name ne $profile->{name} ) {
        my $original_path = sql_dashboard_profile_path($original_name);
        unlink $original_path if -f $original_path;
    }
    open my $fh, '>', $path or die "Unable to write $path: $!";
    print {$fh} json_encode($profile);
    close $fh or die "Unable to close $path: $!";
    sql_dashboard_secure_file($path);
    $payload = {
        ok      => 1,
        profile => $profile,
    };
    1;
} or do {
    my $error = $@ || 'Unknown sql-dashboard profile save failure';
    $error =~ s/\s+\z//;
    $payload = {
        ok    => 0,
        error => $error,
    };
};
print j $payload;
PERL
}

# _sql_dashboard_profiles_delete_code()
# Builds the saved sql-dashboard profile-delete Ajax handler.
# Input: none.
# Output: Perl source string.
sub _sql_dashboard_profiles_delete_code {
    return _sql_dashboard_profile_helpers() . <<'PERL';
my $payload;
eval {
    my $name = sql_dashboard_trim( params->{name} );
    die "Missing profile name\n" if $name eq '';
    my $path = sql_dashboard_profile_path($name);
    unlink $path or die "Unable to remove $path: $!" if -e $path;
    $payload = {
        ok      => 1,
        removed => $name,
    };
    1;
} or do {
    my $error = $@ || 'Unknown sql-dashboard profile delete failure';
    $error =~ s/\s+\z//;
    $payload = {
        ok    => 0,
        error => $error,
    };
};
print j $payload;
PERL
}

# _sql_dashboard_collections_save_code()
# Builds the saved sql-dashboard collection-save Ajax handler.
# Input: none.
# Output: Perl source string.
sub _sql_dashboard_collections_save_code {
    return _sql_dashboard_profile_helpers() . <<'PERL';
my $payload;
eval {
    my $collection = sql_dashboard_decode_collection_payload( params->{collection} );
    my $original_name = sql_dashboard_trim( params->{original_name} );
    my $path = sql_dashboard_collection_path( $collection->{name} );
    if ( $original_name ne '' && $original_name ne $collection->{name} ) {
        my $original_path = sql_dashboard_collection_path($original_name);
        unlink $original_path if -f $original_path;
    }
    open my $fh, '>', $path or die "Unable to write $path: $!";
    print {$fh} json_encode($collection);
    close $fh or die "Unable to close $path: $!";
    sql_dashboard_secure_file($path);
    $payload = {
        ok         => 1,
        collection => $collection,
    };
    1;
} or do {
    my $error = $@ || 'Unknown sql-dashboard collection save failure';
    $error =~ s/\s+\z//;
    $payload = {
        ok    => 0,
        error => $error,
    };
};
print j $payload;
PERL
}

# _sql_dashboard_collections_delete_code()
# Builds the saved sql-dashboard collection-delete Ajax handler.
# Input: none.
# Output: Perl source string.
sub _sql_dashboard_collections_delete_code {
    return _sql_dashboard_profile_helpers() . <<'PERL';
my $payload;
eval {
    my $name = sql_dashboard_trim( params->{name} );
    die "Missing collection name\n" if $name eq '';
    my $path = sql_dashboard_collection_path($name);
    unlink $path or die "Unable to remove $path: $!" if -e $path;
    $payload = {
        ok      => 1,
        removed => $name,
    };
    1;
} or do {
    my $error = $@ || 'Unknown sql-dashboard collection delete failure';
    $error =~ s/\s+\z//;
    $payload = {
        ok    => 0,
        error => $error,
    };
};
print j $payload;
PERL
}

# _sql_dashboard_execute_code()
# Builds the saved sql-dashboard SQL execution Ajax handler.
# Input: none.
# Output: Perl source string.
sub _sql_dashboard_execute_code {
    return _sql_dashboard_runtime_helpers() . <<'PERL';
my $payload;
eval {
    my $settings = json_decode( params->{settings} || '{}' );
    my $resolved = sql_dashboard_resolve_settings($settings);
    my $sql_text = $settings->{sql} // '';
    $sql_text = sql_dashboard_trim($sql_text);
    die "Missing SQL\n" if $sql_text eq '';
    my $sqls_sep        = ':------------------------------------------------------------------------------:';
    my $instruction_sep = ':~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~:';
    my $blocks = sql_dashboard_parse_sql_blocks( $sql_text, $sqls_sep, $instruction_sep );
    die "No SQL statements were parsed\n" if !@{$blocks};
    my $dbh = sql_dashboard_connect($resolved);
    my %stash;
    my @results;
    my @html;
    for my $block ( @{$blocks} ) {
        my $sql = $block->{sql};
        my $sth = $dbh->prepare($sql);
        $sth->execute();
        my $columns = $sth->{NAME} || [];
        for my $stash_update ( @{ $block->{instructions}{stash} || [] } ) {
            %stash = ( %stash, %{$stash_update} );
        }
        if ( $block->{instructions}{before} ) {
            $block->{instructions}{before}->( $columns, $sth, $dbh, \%stash );
        }
        my @rows;
        my $row_number = 0;
        if ( @{$columns} ) {
            while ( my $row = $sth->fetchrow_hashref ) {
                $row_number++;
                my $options = {};
                if ( $block->{instructions}{row} ) {
                    my $row_result = $block->{instructions}{row}->( $row, \$row_number, $columns, $sth, $dbh, \%stash );
                    $options = $row_result if ref($row_result) eq 'HASH';
                }
                next if $options->{next};
                push @rows, {
                    row_number => $row_number,
                    hl         => $options->{hl} || '',
                    values     => { %{$row} },
                };
            }
        }
        if ( $block->{instructions}{after} ) {
            $block->{instructions}{after}->( $columns, $dbh, \%stash );
        }
        my $rows_affected = @{$columns} ? scalar(@rows) : $sth->rows;
        my $statement_html = sql_dashboard_render_table_html( $columns, \@rows, $sql, $rows_affected );
        push @html, $statement_html;
        push @results, {
            sql           => $sql,
            row_count     => $rows_affected,
            columns       => $columns,
            rows          => \@rows,
            rows_affected => @{$columns} ? 0 : $rows_affected,
        };
    }
    $dbh->disconnect if $dbh && $dbh->can('disconnect');
    $payload = {
        ok      => 1,
        html    => join( "\n", @html ),
        results => \@results,
        details => {
            profile_name  => $resolved->{profile_name} || '',
            connection_id => $resolved->{connection_id} || '',
            driver        => $resolved->{driver},
            dsn           => $resolved->{dsn},
            user          => $resolved->{user},
        },
    };
    1;
} or do {
    my $error = $@ || 'Unknown sql-dashboard execution failure';
    $error =~ s/\s+\z//;
    $payload = {
        ok    => 0,
        error => $error,
        html  => '',
        results => [],
    };
};
print j $payload;
PERL
}

# _sql_dashboard_schema_code()
# Builds the saved sql-dashboard schema-browser Ajax handler.
# Input: none.
# Output: Perl source string.
sub _sql_dashboard_schema_code {
    return _sql_dashboard_runtime_helpers() . <<'PERL';
my $payload;
eval {
    my $settings = json_decode( params->{settings} || '{}' );
    my $resolved = sql_dashboard_resolve_settings($settings);
    my $dbh = sql_dashboard_connect($resolved);
    my $table_sth = $dbh->table_info( undef, undef, '%', 'TABLE' );
    $table_sth->execute() if $table_sth->can('execute');
    my @tables;
    while ( my $row = $table_sth->fetchrow_hashref ) {
        next if !defined $row->{TABLE_NAME} || $row->{TABLE_NAME} eq '';
        push @tables, {
            TABLE_NAME => $row->{TABLE_NAME},
        };
    }
    @tables = sort { $a->{TABLE_NAME} cmp $b->{TABLE_NAME} } @tables;
    my $selected_table = sql_dashboard_trim( $settings->{table_name} );
    $selected_table = $tables[0]{TABLE_NAME} if $selected_table eq '' && @tables;
    my @columns;
    if ( $selected_table ne '' ) {
        my $column_sth = $dbh->column_info( undef, undef, $selected_table, '%' );
        $column_sth->execute() if $column_sth->can('execute');
        while ( my $row = $column_sth->fetchrow_hashref ) {
            push @columns, {
                TABLE_NAME  => $selected_table,
                COLUMN_NAME => $row->{COLUMN_NAME},
                DATA_TYPE   => $row->{DATA_TYPE},
                DATA_LENGTH => $row->{DATA_LENGTH},
            };
        }
    }
    $dbh->disconnect if $dbh && $dbh->can('disconnect');
    $payload = {
        ok             => 1,
        selected_table => $selected_table,
        tables         => \@tables,
        columns        => \@columns,
    };
    1;
} or do {
    my $error = $@ || 'Unknown sql-dashboard schema failure';
    $error =~ s/\s+\z//;
    $payload = {
        ok             => 0,
        error          => $error,
        selected_table => '',
        tables         => [],
        columns        => [],
    };
};
print j $payload;
PERL
}

# _shell_dashboard_command()
# Builds a shell-safe command that re-invokes the current dashboard entrypoint.
# Input: none.
# Output: shell command string suitable for generated shell bootstrap helpers.
sub _shell_dashboard_command {
    my ($shell) = @_;
    my $script = File::Spec->rel2abs($0);
    my $repo_lib = File::Spec->rel2abs( File::Spec->catdir( $Bin, '..', 'lib' ) );
    if ( -f File::Spec->catfile( $repo_lib, 'Developer', 'Dashboard.pm' ) ) {
        if ( $shell eq 'powershell' || $shell eq 'pwsh' ) {
            return join ' ',
              '&',
              shell_quote_for( $shell, $^X ),
              shell_quote_for( $shell, '-I' . $repo_lib ),
              shell_quote_for( $shell, $script );
        }
        return join ' ',
          shell_quote_for( $shell, $^X ),
          '-I' . shell_quote_for( $shell, $repo_lib ),
          shell_quote_for( $shell, $script );
    }
    return shell_quote_for( $shell, $script ) if $shell eq 'powershell' || $shell eq 'pwsh';
    return shell_quote_for( $shell, $script );
}

# _shell_bootstrap($shell)
# Builds the requested shell bootstrap script with shared navigation helpers and
# shell-specific prompt wiring.
# Input: normalized shell name.
# Output: shell bootstrap script string.
sub _shell_bootstrap {
    my ($shell) = @_;
    my $dashboard_cmd = _shell_dashboard_command($shell);
    my $bootstrap = _shell_navigation_bootstrap($shell) . "\n" . _shell_prompt_bootstrap($shell);
    $bootstrap =~ s/__DASHBOARD_CMD__/$dashboard_cmd/g;
    return $bootstrap;
}

# _shell_navigation_bootstrap()
# Builds the portable shell helpers used by all supported interactive shells for
# bookmark-aware navigation.
# Input: none.
# Output: shell function definitions as a script string.
sub _shell_navigation_bootstrap {
    my ($shell) = @_;
    if ( $shell eq 'powershell' || $shell eq 'pwsh' ) {
        return <<'POWERSHELL';
function Get-DashboardTarget {
  param([string[]]$DashboardArgs)
  if (-not $DashboardArgs -or $DashboardArgs.Count -eq 0) {
    return $null
  }
  $target = (__DASHBOARD_CMD__ path resolve $DashboardArgs[0] 2>$null | Select-Object -First 1)
  if (-not $target) {
    $json = __DASHBOARD_CMD__ path locate @DashboardArgs
    if ($LASTEXITCODE -eq 0 -and $json) {
      $parsed = $json | ConvertFrom-Json
      if ($parsed -is [System.Array]) {
        $target = $parsed[0]
      }
      elseif ($parsed) {
        $target = $parsed
      }
    }
  }
  if ($target) {
    return [string]$target
  }
  return $null
}

function cdr {
  param([Parameter(ValueFromRemainingArguments = $true)][string[]]$DashboardArgs)
  $target = Get-DashboardTarget -DashboardArgs $DashboardArgs
  if ($target) {
    Set-Location $target
  }
}

Set-Alias dd_cdr cdr

function which_dir {
  param([Parameter(ValueFromRemainingArguments = $true)][string[]]$DashboardArgs)
  if (-not $DashboardArgs -or $DashboardArgs.Count -eq 0) {
    return
  }
  $resolved = (__DASHBOARD_CMD__ path resolve $DashboardArgs[0] 2>$null | Select-Object -First 1)
  if ($resolved) {
    $resolved
    return
  }
  __DASHBOARD_CMD__ path locate @DashboardArgs
}
POWERSHELL
    }
    return <<'SH';
cdr() {
  target="$(__DASHBOARD_CMD__ path resolve "$1" 2>/dev/null || true)"
  if [ -z "$target" ]; then
    target="$(__DASHBOARD_CMD__ path locate "$@" | perl -MJSON::XS -0777 -ne 'my $a=JSON::XS->new->decode($_); print $a->[0] // q{}')"
  fi
  if [ -n "$target" ]; then
    cd "$target"
  fi
}

dd_cdr() {
  cdr "$@"
}

which_dir() {
  __DASHBOARD_CMD__ path resolve "$1" 2>/dev/null || __DASHBOARD_CMD__ path locate "$@"
}
SH
}

# _shell_prompt_bootstrap($shell)
# Builds the prompt hook snippet for a supported shell so dashboard ps1 can be
# reused outside bash as well.
# Input: normalized shell name.
# Output: shell snippet string.
sub _shell_prompt_bootstrap {
    my ($shell) = @_;
    if ( $shell eq 'bash' ) {
        return <<'BASH';

export PS1='$(__DASHBOARD_CMD__ ps1 --jobs \j --mode compact)'
BASH
    }
    if ( $shell eq 'zsh' ) {
        return <<'ZSH';

autoload -Uz add-zsh-hook
_dd_update_prompt() {
  PS1="$(__DASHBOARD_CMD__ ps1 --jobs ${#jobstates} --mode compact)"
}
add-zsh-hook precmd _dd_update_prompt
_dd_update_prompt
ZSH
    }
    if ( $shell eq 'sh' ) {
        return <<'SH';

_dd_prompt_command() {
  __DASHBOARD_CMD__ ps1 --mode compact
}
PS1='$(_dd_prompt_command)'
export PS1
SH
    }
    if ( $shell eq 'powershell' || $shell eq 'pwsh' ) {
        return <<'POWERSHELL';
function prompt {
  __DASHBOARD_CMD__ ps1 --mode compact
}
POWERSHELL
    }
    die "Unsupported shell '$shell'\n";
}

# _resolve_directory_runner($dir)
# Resolves the runnable command file used by one directory-backed custom command.
# Input: command directory path.
# Output: runnable file path string or undef when the directory has no known runner.
sub _resolve_directory_runner {
    my ($dir) = @_;
    return if !defined $dir || $dir eq '' || !-d $dir;
    for my $name (qw(run run.pl run.ps1 run.cmd run.bat run.sh run.bash)) {
        my $path = File::Spec->catfile( $dir, $name );
        my $resolved = resolve_runnable_file($path);
        return $resolved if $resolved;
    }
    return;
}

pod2usage(
    -exitval => 1,
    -verbose => 99,
    -sections => [ qw(NAME SYNOPSIS) ],
);

__END__

=head1 NAME

dashboard - command-line entrypoint for Developer Dashboard

=head1 SYNOPSIS

  dashboard help
  dashboard init
  dashboard update
  dashboard doctor [--fix]
  dashboard ps1 [--jobs N] [--cwd PATH] [--mode compact|extended] [--color]
  dashboard paths
  dashboard path list
  dashboard path resolve <name>
  dashboard path add <name> <path>
  dashboard path del <name>
  dashboard path locate <term...>
  dashboard path project-root
  dashboard of [--print] [--line N] [--editor CMD] <file|scope> [pattern...]
  dashboard open-file [--print] [--line N] [--editor CMD] <file|scope> [pattern...]
  dashboard ticket [ticket-ref]
  dashboard jq [path] [file]
  dashboard yq [path] [file]
  dashboard tomq [path] [file]
  dashboard propq [path] [file]
  dashboard iniq [path] [file]
  dashboard csvq [path] [file]
  dashboard xmlq [path] [file]
  dashboard encode < input.txt
  dashboard decode < token.txt
  dashboard indicator set <name> <label> <icon> <status>
  dashboard indicator list
  dashboard indicator refresh-core [cwd]
  dashboard collector write-result <name> <exit_code>
  dashboard collector status <name>
  dashboard collector list
  dashboard collector job <name>
  dashboard collector output <name>
  dashboard collector inspect <name>
  dashboard collector log
  dashboard collector run <name>
  dashboard collector start <name>
  dashboard collector stop <name>
  dashboard collector restart <name>
  dashboard skills install <git-url>
  dashboard skills uninstall <repo-name>
  dashboard skills update <repo-name>
  dashboard skills list
  dashboard skill <repo-name> <command> [args...]
  dashboard config init
  dashboard config show
  dashboard auth add-user <username> <password>
  dashboard auth list-users
  dashboard auth remove-user <username>
  dashboard page new [id] [title]
  dashboard page save <id>
  dashboard page list
  dashboard page show <id>
  dashboard page encode [id]
  dashboard page decode [token]
  dashboard page urls <id>
  dashboard page render [id|file]
  dashboard page source <id|token>
  dashboard action run <page_id> <action_id>
  dashboard docker compose [--addon NAME] [--mode NAME] [--service NAME] [--project DIR] [--dry-run] <compose-args...>
  dashboard serve [logs [-f] [-n N]|workers <N>] [--host HOST] [--port PORT] [--workers N] [--foreground]
  dashboard stop
  dashboard restart [--host HOST] [--port PORT] [--workers N]
  dashboard shell [bash|zsh|sh|ps|powershell|pwsh]
  dashboard <custom-subcommand> [args...]

=head1 DESCRIPTION

Developer Dashboard provides a project-neutral local developer cockpit with:

=over 4

=item *

saved and transient dashboard pages

=item *

file-backed collectors and indicators

=item *

prompt rendering for C<PS1> and the PowerShell C<prompt> function

=item *

runtime permission auditing and repair through C<dashboard doctor>

=item *

background web-service lifecycle management

=item *

trusted and safer page actions

=item *

config-backed providers and aliases

=item *

project-aware Docker Compose resolution

=item *

user CLI extensions loaded from F<~/.developer-dashboard/cli>

=item *

built-in C<dashboard of> / C<dashboard open-file> resolution for direct files,
C<file:line> references, Perl module names, Java class names, and recursive
pattern matching, including the numbered chooser and default editor fallback

=item *

built-in C<dashboard ticket> tmux session attach/create support through a
private runtime helper staged under F<~/.developer-dashboard/cli/ticket>

=item *

built-in C<dashboard jq> / C<dashboard yq> / C<dashboard tomq> /
C<dashboard propq> parsing for JSON, YAML, TOML, and Java properties input

=item *

built-in C<dashboard iniq> / C<dashboard csvq> / C<dashboard xmlq> parsing 
for INI, CSV, and XML file input

=back

Unknown top-level subcommands can be provided by executable files under
F<~/.developer-dashboard/cli>. For example, C<dashboard foobar a b> will exec
F<~/.developer-dashboard/cli/foobar> with C<a b> as argv, while preserving
stdin, stdout, and stderr.

Per-command hook files can also be placed in either
F<~/.developer-dashboard/cli/E<lt>commandE<gt>> or
F<~/.developer-dashboard/cli/E<lt>commandE<gt>.d>. Executable files in that
directory are run in sorted filename order before the real command runs,
their stdout and stderr stream live to the terminal while still being
accumulated into C<$ENV{RESULT}> as JSON, and non-executable files are
skipped. Built-in commands such as C<dashboard jq> use the same hook
location. A directory-backed custom command may provide its real executable as
F<~/.developer-dashboard/cli/E<lt>commandE<gt>/run>; that runner receives the
final C<$ENV{RESULT}> value after the hook files finish. If a subcommand does
not have a built-in implementation, the real command can be supplied as
F<~/.developer-dashboard/cli/E<lt>commandE<gt>> or
F<~/.developer-dashboard/cli/E<lt>commandE<gt>/run>.

Run C<dashboard> with no arguments for the quick synopsis, or C<dashboard help> for the full POD-backed manual.
