The job was registered but never executed: `background-job:list` kept
showing last_run = 1970-01-01. It was marked TIME_INSENSITIVE, and
OC\Core\Service\CronService::runCli() reads `maintenance_window_start` and
calls jobList->getNext($onlyTimeSensitive = true) whenever the current UTC
hour is outside [start, start+4] -- so with a maintenance window configured,
time-insensitive jobs are skipped for the other 20 hours of the day.
A job whose entire purpose is a 5-minute (now configurable down to 60s)
reaction time is time-sensitive by definition.
Also fix the occ invocation in README.md and CLAUDE.md: background-job:worker
takes job classes, not an app id, so the documented
`background-job:worker workflow_deck_automation` matched nothing.
- Boards: BoardService::getUserBoards() defaults to $includeArchived = true,
and that flag also gates the `deleted_at = 0` condition, so archived and
trashed boards showed up in the dropdown. Request the filtered query
instead, with a fallback to the no-arg call if the signature ever changes.
Stacks need nothing: StackMapper::findAll() always filters deleted_at.
- Each stack dropdown now hides whatever the other one holds, so source and
target can no longer be set to the same stack. The save-time check stays
as the backstop for rows stored before this rule.
- RunWorkflowsJob reads its interval from config.php
('workflow_deck_automation.interval', seconds, default 300, clamped to a
60s minimum). TimedJob re-reads the interval on every cron pass, so a
changed value takes effect without any occ command.
The four workflows all triggered on the same push to main and ran fully
independently, so build-main.yml published a latest-main release even when
PHPUnit or the linters were red. `needs:` only works between jobs of one
workflow, so the checks move into build-main.yml and the package job now
depends on them.
Two gaps close along the way: the check workflows only ran on main pushes
and pull requests, so a v* tag was published entirely unverified, and
`npm run build` only ran on main, so no pull request ever exercised the
frontend build -- the workflow now runs on pull_request too, with the
publish step skipped so the job acts as a build check.
Build and publish deliberately stay in a single job; moving the tarball
between jobs would require actions/upload-artifact (unusable on this Gitea,
see CLAUDE.md) or actions/cache.
The settings page rendered, but nothing in it worked:
- All NcSelect dropdowns showed "undefined" for every option. In
@nextcloud/vue 9 the `label` prop is vue-select's option display *key*,
not a caption, so `label="Board"` read `option.Board`. Use `input-label`.
- Saving always failed with "Bitte einen Titel angeben". Vue 3 dropped
`.sync`; NcTextField and NcCheckboxRadioSwitch bind via `modelValue`, so
`:value.sync` / `:checked.sync` never wrote back. Use `v-model`.
- NcButton's style prop is now `variant`, `type` is the native button type
and `native-type` is gone. `type="tertiary"` rendered
`<button type="tertiary">`, which HTML falls back to `submit` for, making
the cancel button submit the form.
- The user dropdown was always empty. Deck's RelationalEntity replaces
resolved relations with a RelationalObject once an entity is enriched, so
`$acl->getParticipant()` returns that wrapper and the uid lives in
`getPrimaryKey()` -- probing for `getUID()` yielded null. This also broke
the background job's assigned-user filter, which shares the extractor.
- A board's ACL never contains its owner (a private board has an empty
ACL), so participants are now seeded with the owner, group ACL entries
are expanded via IGroupManager and display names resolved via
IUserManager.
- One board appeared twice: getUserBoards() merges own/group/circle boards
and includes archived and trashed ones. Deduplicate by id and drop those.
Also keep one failing lookup in onBoardChange from taking the other two
dropdowns down with it, and document all of the above in CLAUDE.md.
Gitea injects a token into every Actions job as secrets.GITEA_TOKEN, so the
manually created PAT was never necessary. Declare permissions: contents:
write on the job, which Gitea maps to Code: write (deleting the latest-main
tag) and Releases: write (creating the release, uploading the asset) - the
full set the publish step needs.
Removes a secret that had to be created by hand, could expire, and was a
single point of failure the workflow had no fallback for.
build-release.yml and build-main.yml ran identical steps (checkout, node,
npm install, npm run build, make appstore) and differed only in where the
package landed. Fold the v* tag trigger into build-main.yml and branch on
gitea.ref in the publishing step: main keeps the rolling latest-main
pre-release, tags get a normal release on the tag that was just pushed.
Tag deletion is now guarded - only latest-main is recreated, since deleting
a version tag would destroy the ref that triggered the run. A stale release
is still removed in both cases so moved tags republish cleanly.
Side effects: version builds are published as durable release assets with a
stable URL instead of expiring workflow artifacts, which drops the last
actions/upload-artifact usage and with it the @v3 pin forced by the GHES
restriction; and the setup-php step goes away, since make appstore only
shells out to mkdir/tar/rm.
@nextcloud/vite-config prefixes every entry name with the app id, so the
entry "workflow-deck-automation-personal-settings" was emitted as
js/workflow_deck_automation-workflow-deck-automation-personal-settings.mjs
while Util::addScript() was still asking for the unprefixed name. Nextcloud
found neither the script nor the style, so only the empty mount div rendered
and the settings page stayed blank.
Shorten the entry to "personal-settings" and pass the full on-disk basename
(APP_ID . '-personal-settings') to addScript()/addStyle(). Document the
prefix rule and the intentionally near-empty CSS entry stub in CLAUDE.md.
Publishes the appstore package as a Gitea release asset on every push
to main, so a deployable build can be grabbed without cutting a
version tag first.
v4+ uses the @actions/artifact v2 backend, which this self-hosted
Gitea instance rejects as unsupported since it identifies as GHES to
the action's GHES-detection check. checkout/setup-node don't have
this issue and stay on v7.
actions/checkout v4 -> v7, actions/setup-node v4 -> v7,
actions/upload-artifact v4 -> v7 across all workflows.
shivammathur/setup-php stays on the floating @v2 tag, which already
resolves to the latest 2.x release.
The self-hosted Gitea runner's image has no rsync installed. tar is
a core utility present on essentially any Linux base image, and its
--exclude on a directory prevents descending into it at all, which
also avoids the build/ output dir (itself inside the source tree)
being copied into itself.
v9 restricts subpath imports via package.json "exports" to
@nextcloud/vue/components/Xxx; the old dist/Components/Xxx.js deep
import path used throughout PersonalSettings.vue is no longer
resolvable under Node's ESM export map and failed the build.
vite-plugin-dts peer-depends on typescript: "*" but nothing installed
it, so it crashed at module-load time before tsconfig.json was ever
consulted. The earlier tsconfig.json fix alone was insufficient.
@nextcloud/vite-config's index.js barrel statically re-exports
createLibConfig from libConfig.js, which imports vite-plugin-dts at
module scope. That import chain runs even though we only use
createAppConfig, and vite-plugin-dts crashes while resolving a
tsconfig that did not exist in this (plain JS/Vue) project.
vite.config.js was being loaded as CommonJS by default, so Vite's
config bundler tried to require() the ESM-only @nextcloud/vite-config
and failed. All our JS already uses import/export syntax.
@nextcloud/vite-config@2.5.4 (resolved from our ^2.2.0 range) requires
vite@^7.3.6 as a peer; we had vite pinned to ^6.0.0, causing an
ERESOLVE conflict during npm install.
config.platform.php was pinned to 8.2, but nextcloud/ocp:dev-master
requires PHP 8.3+, so Composer failed to resolve even though the
runner's actual PHP (8.3.33) satisfies it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ports the standalone due-date cron script into a proper Nextcloud 34
app with a personal-settings UI, per-user workflow configuration
(source/target stack, assigned-user and label filters, email
notification), a TimedJob background runner, and Gitea CI pipelines.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>