diff --git a/Dockerfile.optimized b/Dockerfile.optimized
new file mode 100644
index 0000000000000000000000000000000000000000..9f2ff15f8fbe7dd159d8864d2bd15007ca092fce
--- /dev/null
+++ b/Dockerfile.optimized
@@ -0,0 +1,310 @@
+# ─────────────────────────────────────────────────────────────
+# Multi-stage Dockerfile for Elixir Phoenix Applications
+# Highly optimized for Debian Bullseye with aggressive caching
+# Supports lazy_html, lexbor, vix, and other native dependencies
+# ─────────────────────────────────────────────────────────────
+
+# Build arguments - pinned versions for reproducible builds
+ARG ELIXIR_VERSION=1.18.4
+ARG OTP_VERSION=28.0.4
+ARG DEBIAN_VERSION=bullseye-20250908-slim
+ARG NODE_VERSION=20
+
+ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
+ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
+ARG NODE_IMAGE="node:${NODE_VERSION}-${DEBIAN_VERSION}"
+
+# ═════════════════════════════════════════════════════════════
+# STAGE 0: Base system dependencies layer (aggressive caching)
+# ═════════════════════════════════════════════════════════════
+FROM ${BUILDER_IMAGE} as base-deps
+
+# Install system dependencies in optimized layers for maximum caching
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ ca-certificates \
+ curl \
+ git \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Core build tools layer
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ build-essential \
+ cmake \
+ make \
+ gcc \
+ g++ \
+ pkg-config \
+ autoconf \
+ libtool \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Development libraries layer
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ libc6-dev \
+ libssl-dev \
+ libncurses5-dev \
+ zlib1g-dev \
+ libxml2-dev \
+ libxslt1-dev \
+ libcurl4-openssl-dev \
+ libpcre3-dev \
+ libffi-dev \
+ libglib2.0-dev \
+ libexpat1-dev \
+ libmagic-dev \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Image processing libraries layer (vix dependencies)
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ libjpeg-dev \
+ libpng-dev \
+ libtiff-dev \
+ libwebp-dev \
+ libheif-dev \
+ liborc-0.4-dev \
+ libvips-dev \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Set build environment
+ENV MIX_ENV="prod"
+ENV ERL_FLAGS="+JPperf true"
+
+# Install hex and rebar once and cache
+RUN mix local.hex --force && \
+ mix local.rebar --force
+
+# ═════════════════════════════════════════════════════════════
+# STAGE 1: Node.js dependencies (parallel caching)
+# ═════════════════════════════════════════════════════════════
+FROM ${NODE_IMAGE} as node-deps
+
+WORKDIR /app
+
+# Copy and install Node.js dependencies first (better caching)
+COPY assets/package*.json assets/
+RUN cd assets && npm ci --only=production --no-audit --no-fund
+
+# ═════════════════════════════════════════════════════════════
+# STAGE 2: Elixir dependencies (parallel caching)
+# ═════════════════════════════════════════════════════════════
+FROM base-deps as elixir-deps
+
+WORKDIR /app
+
+# Copy dependency files for better layer caching
+COPY mix.exs mix.lock ./
+
+# Download dependencies first (cached layer)
+RUN mix deps.get --only $MIX_ENV
+
+# Create minimal config structure
+RUN mkdir -p config
+
+# Copy config files needed for compilation (separate layer)
+COPY config/config.exs config/${MIX_ENV}.exs config/runtime.exs config/
+
+# Compile dependencies in separate layer for better caching
+RUN mix deps.compile
+
+# ═════════════════════════════════════════════════════════════
+# STAGE 3: Assets compilation (using cached node modules)
+# ═════════════════════════════════════════════════════════════
+FROM elixir-deps as assets-builder
+
+# Copy Node.js dependencies from parallel stage
+COPY --from=node-deps /app/assets/node_modules assets/node_modules
+
+# Copy assets source
+COPY assets assets
+COPY priv priv
+
+# Build assets with Node.js from node-deps stage
+COPY --from=node-deps /usr/local/bin/node /usr/local/bin/node
+COPY --from=node-deps /usr/local/bin/npm /usr/local/bin/npm
+
+# Compile assets
+RUN mix assets.setup 2>/dev/null || true
+RUN mix assets.deploy
+
+# ═════════════════════════════════════════════════════════════
+# STAGE 4: Application compilation (using pre-compiled deps)
+# ═════════════════════════════════════════════════════════════
+FROM elixir-deps as app-builder
+
+# Copy compiled assets from assets stage
+COPY --from=assets-builder /app/priv/static priv/static
+
+# Copy application source
+COPY lib lib
+COPY config config
+
+# Copy additional files needed for compilation
+COPY .formatter.exs .credo.exs ./
+
+# Compile application (deps already compiled in previous stage)
+RUN mix compile --force
+
+# Build release with optimizations
+RUN mix release --overwrite
+
+# ═════════════════════════════════════════════════════════════
+# STAGE 5: Runtime image (minimal and optimized)
+# ═════════════════════════════════════════════════════════════
+FROM ${RUNNER_IMAGE} as runtime
+
+# Install only runtime dependencies in minimal layers
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ ca-certificates \
+ curl \
+ openssl \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Runtime libraries
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ libstdc++6 \
+ libncurses5 \
+ zlib1g \
+ libxml2 \
+ libxslt1.1 \
+ libpcre3 \
+ tzdata \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Image processing runtime libraries (for vix)
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ libvips42 \
+ libheif1 \
+ libwebp6 \
+ libjpeg62-turbo \
+ libpng16-16 \
+ libtiff5 \
+ libffi6 \
+ libglib2.0-0 \
+ libexpat1 \
+ libmagic1 \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Create app user
+RUN groupadd --gid 1000 app && \
+ useradd --uid 1000 --gid app --shell /bin/bash --create-home app
+
+# Create app directory
+WORKDIR /app
+
+# Set runtime environment
+ENV MIX_ENV="prod"
+ENV PHX_SERVER="true"
+ENV ERL_FLAGS="+JPperf true"
+
+# Copy release from builder stage
+COPY --from=app-builder --chown=app:app /app/_build/prod/rel/sig ./
+
+# Switch to app user
+USER app
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD curl -f http://localhost:4000/health || exit 1
+
+# Expose port
+EXPOSE 4000
+
+# Start the application
+CMD ["./bin/sig", "start"]
+
+# ═════════════════════════════════════════════════════════════
+# STAGE 6: Development image (cached layers reuse)
+# ═════════════════════════════════════════════════════════════
+FROM base-deps as development
+
+# Add development-specific tools
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ inotify-tools \
+ postgresql-client \
+ python3 \
+ python3-pip \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Add Node.js for development
+COPY --from=node-deps /usr/local/bin/node /usr/local/bin/node
+COPY --from=node-deps /usr/local/bin/npm /usr/local/bin/npm
+COPY --from=node-deps /usr/local/lib/node_modules /usr/local/lib/node_modules
+RUN ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm
+
+# Set development environment
+ENV MIX_ENV="dev"
+ENV ERL_FLAGS="+JPperf true"
+
+WORKDIR /app
+
+# Copy all files for development
+COPY . .
+
+# Install all dependencies (including dev/test)
+RUN mix deps.get
+
+# Compile application
+RUN mix compile
+
+# Install assets
+RUN mix assets.setup 2>/dev/null || true
+
+# Expose ports for development
+EXPOSE 4000 4001
+
+# Default command for development
+CMD ["mix", "phx.server"]
+
+# ═════════════════════════════════════════════════════════════
+# STAGE 7: CI/Test image (optimized for testing)
+# ═════════════════════════════════════════════════════════════
+FROM base-deps as ci
+
+# Add CI-specific tools
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ postgresql-client \
+ python3 \
+ python3-pip \
+ bash \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Set test environment
+ENV MIX_ENV="test"
+ENV ERL_FLAGS="+JPperf true"
+
+WORKDIR /app
+
+# Copy dependency files first for better caching
+COPY mix.exs mix.lock ./
+RUN mix deps.get
+
+# Copy config
+COPY config config
+
+# Copy source and test files
+COPY lib lib
+COPY test test
+COPY priv priv
+COPY assets assets
+
+# Copy configuration files
+COPY .formatter.exs .credo.exs .sobelow-conf ./
+
+# Compile dependencies and application
+RUN mix deps.compile
+RUN mix compile
+
+# Setup assets for testing
+RUN mix assets.setup 2>/dev/null || true
+
+# Default command for CI
+CMD ["mix", "test"]
\ No newline at end of file
diff --git a/config/runtime.exs b/config/runtime.exs
index 8bda56a4097dc3d582009c591c966f6f451f91cf..ec4ba4bfa580c0cf9c77b8ce5a951ceb36403f92 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -55,14 +55,15 @@ if config_env() == :prod do
config :sig, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
+ # Configure IP binding based on Fly.io requirements
+ # Fly.io requires binding to 0.0.0.0 (IPv4) for HTTP traffic
+ bind_ip = if System.get_env("FLY_APP_NAME"), do: {0, 0, 0, 0}, else: {127, 0, 0, 1}
+
config :sig, SigWeb.Endpoint,
url: [host: host, port: 443, scheme: "https"],
http: [
- # Enable IPv6 and bind on all interfaces.
- # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
- # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0
- # for details about using IPv6 vs IPv4 and loopback vs public addresses.
- ip: {0, 0, 0, 0, 0, 0, 0, 0},
+ # Bind on all interfaces for Fly.io deployment
+ ip: bind_ip,
port: port
],
secret_key_base: secret_key_base
diff --git a/lib/blackboard.ex b/lib/blackboard.ex
index 15b8147efb79fe858e98480df51c97d5342a5a64..f0f90464382310201ac7f44e2b7b4a7e049a90fc 100644
--- a/lib/blackboard.ex
+++ b/lib/blackboard.ex
@@ -252,6 +252,7 @@ defmodule Blackboard do
]
case find_messages(filters, opts) do
+ {:ok, results} when is_list(results) -> Enum.map(results, & &1.value)
{:error, _, _} -> []
results when is_list(results) -> Enum.map(results, & &1.value)
end
diff --git a/lib/sig/chat.ex b/lib/sig/chat.ex
index f92759eba36936c9a30befb4a8e1f15b939b314b..5eef71125f568e6cc019aae0c9001a3ab58651c5 100644
--- a/lib/sig/chat.ex
+++ b/lib/sig/chat.ex
@@ -221,6 +221,8 @@ defmodule Sig.Chat do
defp determine_message_type(content, opts) do
cond do
+ # Explicit type specified in options (for IRC commands like /me, system messages, etc.)
+ Keyword.get(opts, :type) -> Keyword.get(opts, :type)
Keyword.get(opts, :llm_backend) -> :llm_response
String.starts_with?(content, "/") -> :command
String.contains?(content, "@") -> :mention
diff --git a/lib/sig_web/components/navigation.ex b/lib/sig_web/components/navigation.ex
index 85e641b65ec69d53e5bb5a3b789d39019ae5a96d..53de7fc0b7ea5ee75628e44db578ffe536567466 100644
--- a/lib/sig_web/components/navigation.ex
+++ b/lib/sig_web/components/navigation.ex
@@ -296,7 +296,7 @@ defmodule SigWeb.Components.Navigation do
D
- {@user.name}
+ {if is_map(@user), do: @user.name, else: @user}