Files
Panama/tests/setup/launcher-search-contract
T
Gabriel Brown 89417cd6d4 Take Claude Desktop from the repository that now carries it
It was `panama app claude-desktop`: a source build, because nothing packaged it.
Upstream publishes an RPM repository now, so the exception shrinks to adding
that repository -- and the result upgrades with `dnf upgrade` along with
everything else instead of needing a slow rebuild every time a version ships.

That moves it out of setup/apps/ and into the third-party section of
install-packages, beside Bun, Claude Code and RustDesk. It also means it is part
of ./install for the first time. The reason it was kept out was the build: slow,
wants the network throughout, and fails on an upstream that moved, which is the
failure the interview exists to prevent. A dnf install is none of those, and the
default dock pins com.anthropic.Claude, so a fresh machine was shipping a pin
for something the install had never put there.

The repository is added with upstream's own setup script rather than a .repo
file written out here. A baseurl copied into this repository is a pin by another
name; the script is the part upstream keeps correct. Both halves are skipped
when already present, so a re-run costs nothing.

The desktop entry id does not change -- it is still com.anthropic.Claude -- so
the dock pin and the notification rules carry over untouched. The new package
drops the nodejs dependency, which is what used to hide the missing-npm bug the
launcher search contract guards; its comment said "depends" in the present
tense and now says what actually happened.
2026-08-21 14:06:26 -04:00

129 lines
6.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Searching the web from the launcher, in both the forms Panama ships it.
#
# There are two, deliberately: a script command that needs nothing but bash, and
# an extension that adds live suggestions but has to be compiled. That is also
# the risk this pins -- the search engine is written down twice, and two copies
# of a URL drift.
#
# `vicinae script check` is the validator for the first, and it exits 0 even when
# it rejects a file. Checking its exit status would pass on a script Vicinae
# refuses to load, so its output is what counts.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
script="$repo_dir/config/local/share/vicinae/scripts/search-web"
extension="$repo_dir/config/local/share/vicinae/extensions/panama-search"
stage="$repo_dir/setup/scripts/link-vicinae-scripts"
findings=()
note() { findings+=("$1"); }
# ── The script command ───────────────────────────────────────────────────────
if [[ ! -x "$script" ]]; then
note 'search-web is missing or not executable, so Vicinae will not load it'
else
if command -v vicinae >/dev/null 2>&1; then
output="$(vicinae script check "$script" 2>&1)"
[[ "$output" == *Error* ]] && note "Vicinae rejects search-web: $output"
fi
# Exactly one argument. Vicinae only offers a no-view command as a fallback
# -- type anything, get "Search" at the bottom -- when it takes a single
# text argument, which is the whole point of shipping this one.
arguments="$(grep -c '@vicinae.argument' "$script" || true)"
(( arguments == 1 )) \
|| note "search-web declares $arguments arguments; a fallback command takes exactly 1"
grep -q '"percentEncoded": true' "$script" \
|| note 'the argument is not percent-encoded, so a query containing & or # is truncated'
grep -qE '@vicinae\.mode silent' "$script" \
|| note 'search-web is not a silent command, so it would render a view it has nothing to put in'
# The default browser is a setting this desktop already owns. Naming a
# browser here would quietly outrank the Applications page.
grep -q 'xdg-open' "$script" \
|| note 'search-web does not open through xdg-open, so it ignores the default browser'
grep -qE '\b(helium|firefox|chromium|google-chrome)\b' "$script" \
&& note 'search-web names a specific browser instead of following the default'
fi
# ── The extension ────────────────────────────────────────────────────────────
manifest="$extension/package.json"
if [[ ! -f "$manifest" ]]; then
note 'the panama-search extension has no manifest'
else
if command -v jq >/dev/null 2>&1; then
jq -e . "$manifest" >/dev/null 2>&1 || note 'the extension manifest is not valid JSON'
for field in name title description commands; do
jq -e "has(\"$field\")" "$manifest" >/dev/null 2>&1 \
|| note "the extension manifest has no \"$field\", which Vicinae requires"
done
# A command's name is its source file. Getting this wrong builds an
# extension with a command that cannot be opened.
while read -r command_name; do
[[ -n "$command_name" ]] || continue
[[ -f "$extension/src/$command_name.tsx" || -f "$extension/src/$command_name.ts" ]] \
|| note "the manifest declares command \"$command_name\" with no matching file in src/"
done < <(jq -r '.commands[]?.name // empty' "$manifest" 2>/dev/null)
fi
fi
# ── One engine, written twice ────────────────────────────────────────────────
#
# The script command and the extension both have to know where a search goes.
# Nothing makes them agree, so this does: searching from the fallback and
# searching from the suggestions list must not reach different places.
engine_in_script="$(grep -oE 'https://[^"]+\?q=' "$script" 2>/dev/null | head -1)"
engine_in_extension="$(grep -oE 'https://[^"]+\?q=' "$extension/src/search.tsx" 2>/dev/null | head -1)"
if [[ -z "$engine_in_script" ]]; then
note 'no search engine URL found in search-web'
elif [[ -z "$engine_in_extension" ]]; then
note 'no search engine URL found in the extension'
elif [[ "$engine_in_script" != "$engine_in_extension" ]]; then
note "the script searches $engine_in_script but the extension searches $engine_in_extension"
fi
# ── Provisioning ─────────────────────────────────────────────────────────────
#
# An extension is compiled, so unlike a script command it cannot simply be
# linked. If nothing builds it, it ships as source nobody can run.
grep -q 'npm run build' "$stage" \
|| note 'no stage builds the Vicinae extensions, so they never reach the launcher'
grep -q 'command -v npm' "$stage" \
|| note 'the extension build does not check for npm, so a machine without it fails the stage'
# Checking for npm is not enough. Node comes from nvm, whose script only an
# interactive shell sources -- and a stage is not one, so npm is absent unless
# the stage goes and gets it. This machine hid that: a system npm existed only
# because the Claude Desktop of the day pulled in nodejs. It no longer does, so
# nothing hides it any more. Without this line, a fresh install builds no
# extension and says so in one line nobody reads.
grep -q '/etc/profile.d/nvm.sh' "$stage" \
|| note 'the extension build never sources nvm, so npm is missing on any machine without a system node'
# node_modules is a dependency tree, not configuration.
git -C "$repo_dir" check-ignore -q "$extension/node_modules" 2>/dev/null \
|| note 'the extension node_modules is not gitignored'
# ── Report ───────────────────────────────────────────────────────────────────
if (( ${#findings[@]} > 0 )); then
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
printf 'launcher search contract: %d finding(s)\n' "${#findings[@]}" >&2
printf ' - %s\n' "${findings[@]}" >&2
exit 1
fi
printf 'launcher search contract: PASS\n'