Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions arch-updater/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Changelog

All notable changes to Arch Updater are documented here. The panel's changelog
icon (the history icon next to Check) shows this same file.

## 2.0.1 - 2026-08-21

- Added: a changelog view in the panel. Click the history icon next to Check to see what changed in each release. It also opens automatically once an update finishes, unless turned off in settings (Show changelog after updating).
- Fixed: hitting Update in terminal mode now closes the panel right away, instead of leaving it open with nothing left to show.
- Fixed: closing the terminal window before an update finished left the panel stuck showing "Updating in a terminal window…" forever. The engine now notices the terminal is gone and reports the run as failed, with the usual retry option.

## 2.0.0 - 2026-08-19

- Added: an update mode setting. Run updates in a terminal window like before, or fully in the background with a live log, progress bar and one polkit password for the whole run.
- Added: an Ignored section in the panel to see and manage packages held back by pacman.conf's IgnorePkg or the plugin's own ignore list.
- Added: update history with per-package and whole-run rollback, resolved against the pacman/AUR cache.
- Added: an opt-in activity graph tracking pending-update counts across recent checks.
- Fixed: the Arch news check re-firing every few seconds after the first run, which could get the whole plugin auto-disabled shortly after login.
- Fixed: pacman's translated "[ignored]"/progress lines breaking the pending count and progress bar on non-English systems.

## 1.1.0 - 2026-08-08

- Added: per-source icons in the package list header.
- Added: an activity graph showing pending-update counts over time, with per-point hover detail.
- Fixed: Dismiss now actually clears the pending list, and hitting Update closes the panel.
- Fixed: tightened package list spacing and icon sizing.

## 1.0.1 - 2026-08-04

- Fixed: reduced CPU work in the Arch news HTTP callback.

## 1.0.0 - 2026-07-28

- Initial release: check pacman, AUR and Flatpak for updates from the panel and the bar widget.
15 changes: 10 additions & 5 deletions arch-updater/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ run is logged and recorded in an update history with per-package rollback.

- `pacman-contrib` on `PATH` (for `checkupdates` and `pactree`), required.
- `pacman`, `sh`, `awk`, `sed`, `grep`, `tail`, `head`, `tee`, `wc`, `date`,
`rm`, `install`, `test` and `uname`, required — base tools from any
standard Arch install (coreutils and friends), used to run and parse the
checks, build the download size estimate, check the running kernel,
follow and open the update log, and install the optional polkit rule.
`rm`, `install`, `test`, `cat`, `kill` and `uname`, required — base tools
from any standard Arch install (coreutils and friends), used to run and
parse the checks, build the download size estimate, check the running
kernel, follow and open the update log, install the optional polkit rule,
and detect whether a terminal update run's process is still alive.
- `pkexec` (polkit) with an authentication agent, required for the
background update mode and for rollback. Noctalia's built-in polkit agent
works out of the box.
Expand Down Expand Up @@ -53,7 +54,11 @@ noctalia msg panel-toggle yuuto/arch-updater:panel
The panel groups pending packages by source (Pacman, AUR, Flatpak). Click a
source row to expand it into its packages. Each package row has an ignore
button (see **Ignored packages**), a copy button (name and versions) and an
open button (its page on archlinux.org, the AUR, or Flathub).
open button (its page on archlinux.org, the AUR, or Flathub). The history
button next to **Check** opens the plugin's changelog, so you can see what
changed in each release without leaving the panel. It also opens on its own
once an update finishes, unless you turn that off with the **Show changelog
after updating** setting.

**Update** follows the **Update mode** setting:

Expand Down
132 changes: 126 additions & 6 deletions arch-updater/panel.luau
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,48 @@ local armedKey = nil -- rollback button waiting for its confirming second click
local armedAt = 0 -- when it was armed; the confirm auto-disarms after a while
local ARM_TIMEOUT_S = 8
local activityHoverIndex = nil -- activity graph point currently under the pointer
local changelogOpen = false -- changelog view replaces the sources
local changelogAutoOpenPending = false -- set with changelogOpen when a just-finished update opened it, so onOpen() doesn't reset it away unseen

local render

local function tr(key, args)
return noctalia.tr(key, args)
end

-- CHANGELOG.md, parsed once at load: "## version — date" headers, "- " bullet
-- lines under each. readFile resolves relative to the plugin directory.
local function parseChangelog(text)
local releases = {}
local current = nil
for line in (text .. "\n"):gmatch("([^\n]*)\n") do
local version, date = line:match("^##%s+(%S+)%s*(.-)%s*$")
if version ~= nil then
-- Strip the "— " (or "- ") separator before the date. Done as a
-- literal gsub rather than folded into the match pattern above,
-- since Lua patterns treat a multi-byte UTF-8 dash inside a
-- character class as a set of individual bytes, not one glyph.
date = date:gsub("^—%s*", ""):gsub("^%-%s*", "")
current = { version = version, date = date, lines = {} }
table.insert(releases, current)
elseif current ~= nil then
local bullet = line:match("^%-%s+(.*)$")
if bullet ~= nil then
table.insert(current.lines, bullet)
end
end
end
return releases
end

local changelog = (function()
local text = noctalia.readFile("CHANGELOG.md")
if type(text) ~= "string" then
return {}
end
return parseChangelog(text)
end)()

-- extra: a package name string, or a table merged into the request payload
-- (pkg/version/at for the rollback family).
local function request(action, extra)
Expand Down Expand Up @@ -768,6 +803,45 @@ local function runViewRows()
return rows
end

-- The changelog view: a back button, then one release per entry in
-- CHANGELOG.md with its bullet points underneath.
local function changelogRows()
local rows = {}
table.insert(rows, ui.row({ key = "changelog-head", gap = 6, align = "center" }, {
ui.button({
glyph = "chevron-left", variant = "ghost", controlSize = "sm", width = 22, height = 22, glyphSize = 12,
tooltip = tr("action_back"),
onClick = function()
changelogOpen = false
render()
end,
}),
ui.label({ text = tr("changelog_title"), fontSize = 12, fontWeight = "bold", color = "on_surface", flexGrow = 1, maxLines = 1 }),
}))
if #changelog == 0 then
table.insert(rows, ui.row({ key = "changelog-empty", paddingH = 18 }, {
ui.label({ text = tr("changelog_empty"), fontSize = 11, color = "on_surface_variant" }),
}))
return rows
end
for ri, release in ipairs(changelog) do
local heading = "v" .. release.version
if release.date ~= nil and release.date ~= "" then
heading = heading .. " · " .. release.date
end
table.insert(rows, ui.row({ key = "changelog-v" .. ri, gap = 6, align = "center" }, {
ui.label({ text = heading, fontSize = 12, fontWeight = "bold", color = "on_surface" }),
}))
for li, bullet in ipairs(release.lines) do
table.insert(rows, ui.row({ key = "changelog-" .. ri .. "-" .. li, paddingH = 10, gap = 6 }, {
ui.label({ text = "•", fontSize = 11, color = "on_surface_variant" }),
ui.label({ text = bullet, fontSize = 11, color = "on_surface_variant", flexGrow = 1, maxLines = 3 }),
}))
end
end
return rows
end

-- Live tail of the update log, with a progress bar while packages are being
-- processed. Shown during a run and kept on screen after a failed one.
local function logSection()
Expand Down Expand Up @@ -906,9 +980,14 @@ local function body()
local children = {}

-- The middle of the panel: the live log while updating (and after a
-- failure), an opened history run, or the package list.
-- failure), the changelog, an opened history run, or the package list.
-- The live log always wins over the changelog: a run in progress is more
-- urgent than release notes.
if phaseOf() == "running" or (runFailed() and #logLines() > 0) then
table.insert(children, logSection())
elseif changelogOpen then
table.insert(children, ui.scroll({ key = "changelog-view", flexGrow = 1, gap = 4 }, changelogRows()))
return children
else
local runRows = openedRunAt ~= nil and runViewRows() or nil
if runRows ~= nil then
Expand Down Expand Up @@ -987,6 +1066,17 @@ render = function()
request("check")
end,
}),
ui.button({
key = "header-changelog" .. (changelogOpen and "-on" or ""),
glyph = "history",
variant = changelogOpen and "primary" or "ghost",
tooltip = tr("tip_changelog"),
onClick = function()
changelogOpen = not changelogOpen
armedKey = nil
render()
end,
}),
ui.button({
glyph = "close", variant = "ghost", tooltip = tr("tip_close"),
onClick = function()
Expand All @@ -1002,8 +1092,10 @@ render = function()
end

-- Both footers hide while a check or run is on screen, so neither ever
-- competes with the live log for space.
local activity = not busy() and activitySection() or nil
-- competes with the live log for space. The activity graph also hides
-- while the changelog is open: it plots pending-update counts, which
-- has nothing to do with release notes.
local activity = not busy() and not changelogOpen and activitySection() or nil
local history = not busy() and historySection() or nil
if activity ~= nil or history ~= nil then
table.insert(children, ui.separator({}))
Expand Down Expand Up @@ -1050,10 +1142,14 @@ render = function()
text = tr("action_update"), variant = "primary", enabled = hasUpdates,
tooltip = backgroundMode and tr("tip_update") or tr("tip_update_terminal"),
onClick = function()
-- The panel stays open: the log section takes over so the
-- run can be watched live (the log is tee'd from the
-- terminal too).
request("update")
if not backgroundMode then
-- Terminal mode hands the run off to its own window, so
-- the panel has nothing left to show; background mode
-- keeps it open since the live log is the only place
-- progress is visible.
panel.close()
end
end,
}))
table.insert(children, ui.row({ gap = 8, align = "center", justify = "end" }, footer))
Expand All @@ -1070,6 +1166,13 @@ function onOpen(_context)
openedRunAt = nil
armedKey = nil
activityHoverIndex = nil
-- A fresh auto-open from a just-finished update (see the state watcher
-- below) survives this reset once, so it's not discarded before it's
-- ever seen; any other reason for opening still defaults to the sources.
if not changelogAutoOpenPending then
changelogOpen = false
end
changelogAutoOpenPending = false
render()
end

Expand All @@ -1094,6 +1197,23 @@ noctalia.state.watch(STATE_KEY, function(value)
if value.phase == "running" and (snapshot == nil or snapshot.phase ~= "running") then
openedRunAt = nil
armedKey = nil
changelogOpen = false
end
-- lastUpdateAt only moves for a real, successful update run (never a
-- rollback or a plain check, see recordUpdateRun in service.luau), and it
-- is bumped the moment the run succeeds, before the auto re-check even
-- starts - so a change here is an exact, one-shot "an update just
-- finished" signal.
local prevUpdateAt = snapshot ~= nil and tonumber(snapshot.lastUpdateAt) or nil
local newUpdateAt = tonumber(value.lastUpdateAt)
if
prevUpdateAt ~= nil
and newUpdateAt ~= nil
and newUpdateAt ~= prevUpdateAt
and noctalia.getConfig("show_changelog_after_update") ~= false
then
changelogOpen = true
changelogAutoOpenPending = true
end
snapshot = value
render()
Expand Down
11 changes: 9 additions & 2 deletions arch-updater/plugin.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
id = "yuuto/arch-updater"
name = "Arch Updater"
version = "2.0.0"
version = "2.0.1"
plugin_api = 9
author = "yuuto"
license = "MIT"
icon = "package"
description = "Check pacman, AUR and Flatpak updates, then upgrade in a terminal or in the background, with run history and rollback."
dependencies = ["pacman-contrib", "awk", "date", "flatpak", "grep", "head", "install", "less", "pacman", "paru", "pkexec", "rm", "sed", "sh", "sudo", "tail", "tee", "test", "uname", "wc", "xdg-open", "yay"]
dependencies = ["pacman-contrib", "awk", "cat", "date", "flatpak", "grep", "head", "install", "kill", "less", "pacman", "paru", "pkexec", "rm", "sed", "sh", "sudo", "tail", "tee", "test", "uname", "wc", "xdg-open", "yay"]
tags = ["arch", "bar", "panel", "launcher", "system", "utility"]

# ── General ──────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -116,6 +116,13 @@ options = [
{ value = "background", label_key = "settings.update_mode.options.background" },
]

[[setting]]
key = "show_changelog_after_update"
type = "bool"
label_key = "settings.show_changelog_after_update.label"
description_key = "settings.show_changelog_after_update.description"
default = true

[[setting]]
key = "rollback_auto_ignore"
type = "bool"
Expand Down
Loading