Skip to content

feat: application auto rules#243

Merged
JoachimLK merged 4 commits into
mainfrom
feat/application-auto-rules
Jul 23, 2026
Merged

feat: application auto rules#243
JoachimLK merged 4 commits into
mainfrom
feat/application-auto-rules

Conversation

@JoachimLK

@JoachimLK JoachimLK commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • What does this PR change?
  • Why is this needed?

PR title must follow Conventional Commits — e.g. feat(jobs): add bulk import or fix: handle null salary. The squash-merged title is what release-please uses to generate the changelog and pick the next version. PRs with non-conventional titles are blocked by CI.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Chore

Validation

  • I tested locally
  • I added/updated relevant documentation
  • I verified multi-tenant scoping and auth behavior for affected API paths

DCO

  • All commits in this PR are signed off (Signed-off-by) via git commit -s

Summary by CodeRabbit

  • New Features

    • Added an Automation tab for each job.
    • Create, edit, enable, disable, reorder, and save application automation rules.
    • Apply rules to existing applicants and automatically set application statuses during submission.
    • Added supported conditions for text, numbers, dates, selections, checkboxes, and file responses.
    • Applications now show automatic-status badges and highlight responses that triggered a rule.
  • Bug Fixes

    • Automatic scoring is skipped when an application is rejected by an automation rule.

Implement automated status categorization and knockout rules for job
applications. This includes a new `application_rule` database schema,
backend evaluation logic during submission, and UI updates to display
automation status and triggering questions to recruiters.
- Create `ApplicationRulesBuilder` component for managing per-job status
  automation rules.
- Add `useApplicationRules` composable to handle rule fetching, atomic
  updates, and triggering retroactive evaluation.
- Implement server-side CRUD and `POST /run` endpoints for rule
  management.
- Define shared `shared/application-rules.ts` as the single source of
  truth for rule evaluation logic, operator metadata, and types.
- Add database migrations for `application_rule` table and `auto_rule`
  metadata on applications.
- Add server-side rule engine to evaluate rules against application
  answers.
@railway-app
railway-app Bot temporarily deployed to applirank / reqcore-pr-243 July 23, 2026 13:15 Destroyed
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@JoachimLK, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 49f1987d-d661-44c7-8d1e-8a531d37148e

📥 Commits

Reviewing files that changed from the base of the PR and between 9be5a6e and 6cd1bb9.

📒 Files selected for processing (11)
  • app/composables/useTimeline.ts
  • server/api/activity-log/candidate-timeline.get.ts
  • server/api/activity-log/timeline.get.ts
  • server/api/jobs/[id]/rules/run.post.ts
  • server/database/migrations/0053_glorious_jigsaw.sql
  • server/database/migrations/meta/0053_snapshot.json
  • server/database/migrations/meta/_journal.json
  • server/database/schema/app.ts
  • server/utils/recordActivity.ts
  • server/utils/rules/applyRules.ts
  • server/utils/schemas/applicationRule.ts
📝 Walkthrough

Walkthrough

Adds configurable application automation rules with shared evaluation logic, database storage, authenticated management APIs, a job-level builder, retroactive execution, submission-time evaluation, and application-detail indicators for automatic decisions.

Changes

Application automation rules

Layer / File(s) Summary
Rule domain and persistence
shared/application-rules.ts, server/utils/schemas/*, server/database/schema/app.ts, server/database/migrations/*, tests/unit/*
Defines rule contracts, operators, evaluation semantics, validation, database storage, migrations, and evaluator tests.
Application rule execution
server/utils/rules/*, server/api/public/jobs/.../apply.post.ts
Evaluates enabled rules against application responses, updates permitted statuses, stores the matched rule, and gates automatic AI scoring for rejected matches.
Rule management endpoints
server/api/jobs/[id]/rules/*
Adds read, transactional replacement, and retroactive execution endpoints with permissions, organization scoping, ordering, logging, and result aggregation.
Rule builder workflow
app/composables/useApplicationRules.ts, app/pages/dashboard/jobs/[id]/rules.vue, app/components/ApplicationRulesBuilder.vue
Adds rule fetching, saving, running, draft editing, validation, condition controls, status selection, and save/run feedback.
Navigation and application indicators
app/components/AppTopBar.vue, app/components/ApplicationBuilder.vue, app/pages/dashboard/jobs/[id]/index.vue
Adds the Automation tab, builder guidance, automatic-status badges, and highlighted triggered responses.


Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is only the template and leaves all required sections unanswered. Replace the template with concrete Summary, Type of change, Validation, and DCO details, including what changed, why, testing, docs, auth, and sign-off.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is conventional, concise, and accurately summarizes the main feature.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/application-auto-rules

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
server/api/jobs/[id]/rules/run.post.ts (1)

37-62: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Retroactive run is unbounded and does redundant per-application work. newApps is fetched without a limit and each iteration calls applyRulesToApplication, which re-loads the job's rules and question-type metadata every time (same data for every application). On a job with a large new backlog this becomes a long, synchronous request doing N×(several queries) work with no timeout.

Consider loading the job's enabled rules + question types once and passing them in, and/or processing in bounded batches or a background job for large sets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/api/jobs/`[id]/rules/run.post.ts around lines 37 - 62, Bound the
retroactive run in the handler around newApps and applyRulesToApplication: load
the job’s enabled rules and question-type metadata once, reuse them for every
application, and process only a bounded batch or enqueue larger backlogs for
background processing. Preserve the existing evaluated, matched, byAction, and
logging behavior for the applications handled by the request, using the relevant
rule-evaluation symbols rather than repeating per-application data-loading
queries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/api/jobs/`[id]/rules/index.put.ts:
- Around line 33-44: Update the insert payload in the transaction around
tx.insert(applicationRule).values so the mapped rules are explicitly typed as
RuleCondition[] before insertion. Preserve the existing field mapping and
displayOrder behavior while ensuring condition values conform to RuleCondition,
including null where supported by the database schema.

In `@server/utils/rules/applyRules.ts`:
- Around line 93-110: The guarded update in applyRulesToApplication must only
report a match when it changes a row. Capture the result of the update
identified by the application status condition, check its affected-row count,
and return null before logInfo and the existing return match when no row was
updated; preserve the current match path when the update succeeds.

In `@server/utils/schemas/applicationRule.ts`:
- Around line 8-18: Update conditionSchema validation used by bulkRulesSchema to
require a non-null, non-empty value for every comparison-based operator,
rejecting empty strings and arrays before rules are saved. Keep value optional
only for presence operators such as is_answered, is_provided, and is_true, while
preserving valid scalar and option-list values for operators that require them.

---

Nitpick comments:
In `@server/api/jobs/`[id]/rules/run.post.ts:
- Around line 37-62: Bound the retroactive run in the handler around newApps and
applyRulesToApplication: load the job’s enabled rules and question-type metadata
once, reuse them for every application, and process only a bounded batch or
enqueue larger backlogs for background processing. Preserve the existing
evaluated, matched, byAction, and logging behavior for the applications handled
by the request, using the relevant rule-evaluation symbols rather than repeating
per-application data-loading queries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c9b2d212-5887-497e-8af5-b6397d58a734

📥 Commits

Reviewing files that changed from the base of the PR and between 54311af and 9be5a6e.

📒 Files selected for processing (20)
  • app/components/AppTopBar.vue
  • app/components/ApplicationBuilder.vue
  • app/components/ApplicationRulesBuilder.vue
  • app/composables/useApplicationRules.ts
  • app/pages/dashboard/jobs/[id]/index.vue
  • app/pages/dashboard/jobs/[id]/rules.vue
  • server/api/jobs/[id]/rules/index.get.ts
  • server/api/jobs/[id]/rules/index.put.ts
  • server/api/jobs/[id]/rules/run.post.ts
  • server/api/public/jobs/[slug]/apply.post.ts
  • server/database/migrations/0051_marvelous_molly_hayes.sql
  • server/database/migrations/0052_application_auto_rule.sql
  • server/database/migrations/meta/0051_snapshot.json
  • server/database/migrations/meta/0052_snapshot.json
  • server/database/migrations/meta/_journal.json
  • server/database/schema/app.ts
  • server/utils/rules/applyRules.ts
  • server/utils/schemas/applicationRule.ts
  • shared/application-rules.ts
  • tests/unit/application-rules.test.ts

Comment on lines +33 to +44
return tx.insert(applicationRule)
.values(body.rules.map((r, index) => ({
organizationId: orgId,
jobId,
name: r.name,
matchType: r.matchType,
action: r.action,
enabled: r.enabled,
conditions: r.conditions,
displayOrder: index,
})))
.returning()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the zod schema and the RuleCondition/enum contracts driving the insert.
fd -t f 'applicationRule.ts' server/utils/schemas --exec cat -n {}
echo '--- RuleCondition / enums ---'
fd -t f 'application-rules.ts' shared --exec sed -n '1,120p' {}
echo '--- column $type ---'
rg -nP -C2 "conditions:\s*jsonb" server/database/schema/app.ts

Repository: reqcore-inc/reqcore

Length of output: 7489


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- target file outline/sections ---'
ast-grep outline 'server/api/jobs/[id]/rules/index.put.ts' --view expanded || true
sed -n '1,120p' 'server/api/jobs/[id]/rules/index.put.ts'

echo '--- nearby zod/type casting in route ---'
rg -n "bulkRulesSchema|zod|applicationRule|insert\\(\\)\\.values|for await|async" 'server/api/jobs/[id]/rules/index.put.ts'

echo '--- package versions mentioning drizzle / zod ---'
for f in package.json pnpm-lock.yaml package-lock.json yarn.lock; do
  [ -f "$f" ] && { echo "### $f"; rg -n "drizzle-orm|zod" "$f" | head -40; }
done

Repository: reqcore-inc/reqcore

Length of output: 4210


Align the zod rule condition type with the DB schema. bulkRulesSchema infers conditionSchema.value as number | undefined, which conflicts with RuleCondition.value allowing null; pass a typed RuleCondition[] payload into tx.insert().values(...) instead of relying on the inferred zod shape.

🧰 Tools
🪛 GitHub Actions: PR Validation / 0_Build, typecheck, and test.txt

[error] 34-34: TypeScript typecheck failed (nuxi typecheck). TS2769: No overload matches this call.

🪛 GitHub Actions: PR Validation / Build, typecheck, and test

[error] 34-34: TypeScript typecheck failed: TS2769: No overload matches this call.

🪛 GitHub Check: Build, typecheck, and test

[failure] 34-34:
No overload matches this call.


[failure] 34-34:
No overload matches this call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/api/jobs/`[id]/rules/index.put.ts around lines 33 - 44, Update the
insert payload in the transaction around tx.insert(applicationRule).values so
the mapped rules are explicitly typed as RuleCondition[] before insertion.
Preserve the existing field mapping and displayOrder behavior while ensuring
condition values conform to RuleCondition, including null where supported by the
database schema.

Sources: Linters/SAST tools, Pipeline failures

Comment thread server/utils/rules/applyRules.ts Outdated
Comment on lines +8 to +18
const conditionSchema = z.object({
questionId: z.string().min(1, 'Pick a question'),
operator: z.enum(ALL_OPERATORS as [string, ...string[]]),
// Comparison value — string, number, or list of option labels. Omitted for
// presence operators (is_answered, is_provided, is_true, …).
value: z.union([
z.string().max(1000),
z.number(),
z.array(z.string().max(500)).max(50),
z.null(),
]).optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate file and search related symbols"
git ls-files | rg 'server/utils/schemas/applicationRule\.ts|applicationRule' || true
wc -l server/utils/schemas/applicationRule.ts

echo
echo "Outline"
ast-grep outline server/utils/schemas/applicationRule.ts || true

echo
echo "Relevant schema/evaluator source"
cat -n server/utils/schemas/applicationRule.ts | sed -n '1,260p'

Repository: reqcore-inc/reqcore

Length of output: 1937


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find applicationRules operator definitions and evaluator"
git ls-files | rg 'application[ _-]?rules|applicationRule' || true

echo
echo "Search ALL_OPERATORS and evaluateCondition"
rg -n "ALL_OPERATORS|evaluateCondition|contains|not_equals|is_not_one_of|is_answered|is_provided|is_true|value:" .

echo
echo "Candidate files with operator definitions"
for f in $(rg -l "ALL_OPERATORS" .); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,260p' "$f"
done

echo
echo "Candidate files with evaluateCondition"
for f in $(rg -l "function evaluateCondition|const evaluateCondition|export .*evaluateCondition|evaluateCondition\\(" .); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,360p' "$f"
done

Repository: reqcore-inc/reqcore

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Shared application-rules evaluator lines 150-220"
sed -n '150,220p' shared/application-rules.ts | cat -n

echo
echo "Unit tests around bad/missing values"
sed -n '1,120p' tests/unit/application-rules.test.ts | cat -n

echo
echo "Behavioral probe of missing/empty comparator values"
node - <<'JS'
const operatorCases = [
  [
    {
      title: 'contains with undefined equals missing question', response: 'foo', value: undefined
    },
    {
      title: 'not_equals with undefined counts unanswered as not-equal', response: undefined, value: undefined
    },
    {
      title: 'is_not_one_of with empty set matches every response', response: 'A', value: []
    },
    {
      title: 'is_one_of with empty set never matches', response: 'A', value: []
    },
    {
      title: 'equals with same value should match', response: 'Yes ', value: 'Yes'
    },
    {
      title: 'not_contains with undefined always true', response: undefined, value: undefined
    }
  ]
]

function toArray(v) {
  if (Array.isArray(v)) return v.map(String)
  if (v === null || v === undefined || v === '') return []
  return [String(v)]
}

function isEmpty(v) {
  if (v === null || v === undefined) return true
  if (typeof v === 'string') return v.trim() === ''
  if (Array.isArray(v)) return v.length === 0
  return false
}

function eqText(a, b) {
  return String(a).trim().toLowerCase() === String(b).trim().toLowerCase()
}

function evaluateCondition(condition) {
  const { operator, value } = condition
  const response = condition.response
  switch (operator) {
    case 'equals': return !isEmpty(response) && eqText(response, value)
    case 'not_equals': return isEmpty(response) || !eqText(response, value)
    case 'contains': return String(response ?? '').toLowerCase().includes(String(value ?? '').toLowerCase())
    case 'not_contains': return !String(response ?? '').toLowerCase().includes(String(value ?? '').toLowerCase())
    case 'is_one_of':
      return toArray(value).map(s => s.toLowerCase()).includes(String(response ?? '').toLowerCase())
    case 'is_not_one_of':
      return !toArray(value).map(s => s.toLowerCase()).includes(String(response ?? '').toLowerCase())
    default: return false
  }
}

for (const c of operatorCases[0]) {
  console.log(`${c.title} => ${evaluateCondition(c)}`)
}
JS

echo
echo "Zod refs for schema exports and bulk validation path"
rg -n "bulkRulesSchema|ruleSchema|conditionSchema|saveApplication|appRule" app shared server/api tests -g '!node_modules' | head -200

Repository: reqcore-inc/reqcore

Length of output: 10937


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
# Read-only behavioral probe equivalent to the evaluator branches for the reported conditions.
response = "Any text response"
missing_response = None
empty_response = ""
response_list = ["A", "B"]
missing_value = None
empty_array = []
def isEmpty(v):
    if v is None or v is missing_response:
        return True
    if isinstance(v, str):
        return v.strip() == ""
    if isinstance(v, list):
        return len(v) == 0
    return False

def toArray(v):
    if isinstance(v, list):
        return [str(item) for item in v]
    if v is None or v is missing_response or isinstance(v, str) and v == "":
        return []
    return [str(v)]

print("contains(undefined value, nonempty response):", str(response).lower().find(str(missing_value).lower()) != -1)
print("not_contains(undefined value, missing response):", not str(matched:=missing_response) and str(response)=str(response)=str(response)=str(response)=str(response)=str(response)=str(response)=str(response)=str(response)=str(response)=str(response)
PY

Repository: reqcore-inc/reqcore

Length of output: 577


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Put route"
cat -n server/api/jobs/[id]/rules/index.put.ts | sed -n '1,40p'

echo
echo "Deterministic probe of missing/empty comparator values in evaluator branches"
node - <<'JS'
function isEmpty(v) {
  if (v === null || v === undefined) return true
  if (typeof v === 'string') return v.trim() === ''
  if (Array.isArray(v)) return v.length === 0
  return false
}

function eqText(a, b) {
  return String(a).trim().toLowerCase() === String(b).trim().toLowerCase()
}

function toArray(v) {
  if (Array.isArray(v)) return v.map(String)
  if (v === null || v === undefined || v === '') return []
  return [String(v)]
}

const cases = [
  { op: 'contains', value: undefined, response: 'Any response text', expected: true },
  { op: 'not_contains', value: undefined, response: undefined, expected: true },
  { op: 'not_equals', value: undefined, response: undefined, expected: true },
  { op: 'is_not_one_of', value: [], response: 'A', expected: true },
  { op: 'includes_any', value: [], response: ['A'], expected: false },
  { op: 'includes_all', value: [], response: ['A'], expected: false },
  { op: 'includes_none', value: [], response: ['A'], expected: true },
  { op: 'contains', value: [], response: 'A', expected: true },
]

for (const c of cases) {
  const { op, value, response } = c
  let result
  if (op === 'contains') result = String(response ?? '').toLowerCase().includes(String(value ?? '').toLowerCase())
  else if (op === 'not_contains') result = !String(response ?? '').toLowerCase().includes(String(value ?? '').toLowerCase())
  else if (op === 'not_equals') result = isEmpty(response) || !eqText(response, value)
  else if (op === 'is_one_of') {
    const set = toArray(value).map(String)
    result = set.map(s => s.toLowerCase()).includes(String(response ?? '').toLowerCase())
  } else if (op === 'is_not_one_of') {
    const set = toArray(value).map(String)
    result = !set.map(s => s.toLowerCase()).includes(String(response ?? '').toLowerCase())
  } else if (op === 'includes_any') {
    result = toArray(response).map(String).map(s => s.toLowerCase()).some(p => toArray(value).map(String).map(v => v.toLowerCase()).includes(p))
  } else if (op === 'includes_all') {
    result = toArray(value).map(String).map(v => v.toLowerCase()).length > 0 && toArray(value).map(String).map(v => v.toLowerCase()).every(w => toArray(response).map(String).map(p => p.toLowerCase()).includes(w))
  } else if (op === 'includes_none') result = !toArray(value).map(String).map(v => v.toLowerCase()).some(w => toArray(response).map(String).map(p => p.toLowerCase()).includes(w))
  console.log(`${op} ${String(value)} vs ${String(response)} => ${result}, expected=${c.expected}`)
}
JS

Repository: reqcore-inc/reqcore

Length of output: 2369


Require values where conditions need them.

bulkRulesSchema accepts omitted/null/empty values for comparison-based operators, and the evaluator turns missing values into broad matches (contains, not_contains, not_equals) or empty set behavior (is_not_one_of). Reject required values by operator and reject empty strings/arrays or null for those comparison conditions before saving rules.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/utils/schemas/applicationRule.ts` around lines 8 - 18, Update
conditionSchema validation used by bulkRulesSchema to require a non-null,
non-empty value for every comparison-based operator, rejecting empty strings and
arrays before rules are saved. Keep value optional only for presence operators
such as is_answered, is_provided, and is_true, while preserving valid scalar and
option-list values for operators that require them.

Import inArray for future use and cast rule operator schema to use
RuleOperator type for better type safety.
@railway-app
railway-app Bot temporarily deployed to applirank / reqcore-pr-243 July 23, 2026 13:23 Destroyed
Comment thread server/utils/rules/applyRules.ts Fixed
- Allow null `actorId` in `activity_log` to represent system actions
- Enable left joins on `activity_log` for timeline views to show system
  entries
- Optimize rule application to process batches with bounded concurrency
- Record audit trails for auto-categorized status changes
@railway-app
railway-app Bot temporarily deployed to applirank / reqcore-pr-243 July 23, 2026 13:31 Destroyed
@railway-app

railway-app Bot commented Jul 23, 2026

Copy link
Copy Markdown

🚅 Deployed to the reqcore-pr-243 environment in applirank

Service Status Web Updated (UTC)
applirank ✅ Success (View Logs) Jul 23, 2026 at 1:34 pm

@JoachimLK
JoachimLK merged commit 73a5516 into main Jul 23, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant