Skip to content

feat: Track application read receipts per user - #266

Merged
JoachimLK merged 6 commits into
mainfrom
feat/viewed-applicants
Aug 9, 2026
Merged

feat: Track application read receipts per user#266
JoachimLK merged 6 commits into
mainfrom
feat/viewed-applicants

Conversation

@JoachimLK

@JoachimLK JoachimLK commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Implement a per-user application_view system to track opened applications.
Replaces the new stage count with a personalized "unviewed" metric to better highlight applications that require attention. Displays an unread-style dot for unopened candidates in the pipeline and tables, and logs views automatically when an application detail is opened.

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 per-user application view tracking with unread indicators.
    • Applications are marked as viewed when opened and remain viewed across sessions.
    • Job lists now show applicants the user has not viewed.
    • Existing applications are initialized as viewed.
  • Bug Fixes
    • Job attention indicators now reflect unviewed applicants.
    • Improved preview environment domain handling and stale URL detection.
  • Tests
    • Added coverage for view tracking and preview URL behavior.

Implement a per-user `application_view` system to track opened
applications.
Replaces the `new` stage count with a personalized "unviewed" metric to
better highlight applications that require attention. Displays an
unread-style dot for unopened candidates in the pipeline and tables, and
logs views automatically when an application detail is opened.
@railway-app

railway-app Bot commented Aug 9, 2026

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
applirank ✅ Success (View Logs) Web Aug 9, 2026 at 3:52 pm

@railway-app
railway-app Bot temporarily deployed to applirank / reqcore-pr-266 August 9, 2026 08:28 Destroyed
@coderabbitai

coderabbitai Bot commented Aug 9, 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: 5 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: 20331605-ce27-4906-a790-8f96c185c31a

📥 Commits

Reviewing files that changed from the base of the PR and between 8d9412f and 5b73a85.

📒 Files selected for processing (6)
  • .github/workflows/railway-pr-domain.yml
  • ARCHITECTURE.md
  • server/database/migrations/0066_demo_application_views.sql
  • server/database/migrations/meta/_journal.json
  • server/utils/applicationViews.ts
  • tests/unit/application-views.test.ts
📝 Walkthrough

Walkthrough

The change adds per-user application view receipts, backfills existing applications, exposes viewed timestamps and unviewed job counts, updates dashboard unread indicators, and improves Railway preview environment linking and URL detection.

Changes

Application view tracking

Layer / File(s) Summary
View receipt storage
server/database/schema/app.ts, server/database/migrations/0065_application_view.sql, server/database/migrations/meta/_journal.json
Adds per-user application view storage, relations, indexes, constraints, migration metadata, and backfilled receipts.
View receipt services and API enrichment
server/utils/applicationViews.ts, server/api/applications/..., server/api/jobs/index.get.ts, tests/unit/application-views.test.ts
Records views, returns viewed timestamps, counts unviewed applications by job, enriches API responses, and tests the utility behavior.
Local application view state
app/composables/useApplicationViews.ts, app/components/ApplicationsList.vue, app/pages/dashboard/jobs/[id]/index.vue
Tracks locally viewed applications, marks opened applications as viewed, and displays unread indicators with viewed-dependent styling.
Job-level unviewed indicators
app/pages/dashboard/jobs/index.vue, CHANGELOG.md
Uses unviewed applicant counts for sorting, grouping, badges, warning styles, attention messages, and changelog documentation.

Preview environment handling

Layer / File(s) Summary
Railway preview domain workflow
.github/workflows/railway-pr-domain.yml
Validates Railway configuration, links prefixed pull-request environments with retries, reports failures, and redeploys after creating a domain.
Preview URL detection
server/utils/auth.ts, tests/unit/preview-auth-url.test.ts
Recognizes Railway pull-request numbers and pull-request environment names when detecting stale inherited URLs.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Recruiter
  participant Dashboard
  participant ApplicationsAPI
  participant ApplicationViews
  participant Database
  Recruiter->>Dashboard: open application
  Dashboard->>ApplicationsAPI: request application details
  ApplicationsAPI->>Database: load application
  ApplicationsAPI->>ApplicationViews: recordApplicationView
  ApplicationViews->>Database: upsert application_view
  ApplicationsAPI-->>Dashboard: return application details
  Dashboard->>Dashboard: mark application viewed locally
  Dashboard-->>Recruiter: render viewed application state
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description summarizes the change, but it leaves the template prompts and all Type of change, Validation, and DCO checkboxes incomplete. Complete the Summary prompts and select applicable Type of change and Validation items; confirm the DCO status for signed-off commits.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature: per-user application read-receipt tracking.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/viewed-applicants

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: 2

🤖 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 `@app/components/ApplicationsList.vue`:
- Around line 420-427: Update the initialApplicationId handling near
selectedApplicationId so it also calls markViewedLocally for the deep-linked
application, matching the behavior of openApplication. Keep the existing direct
selection behavior and useApplicationViews integration unchanged for normal
application openings.

In `@server/api/applications/`[id].get.ts:
- Around line 70-74: Update the application-detail handler around
recordApplicationView so the non-critical receipt write is scheduled without
awaiting it during response construction. Preserve the receipt attempt while
ensuring database latency or failure cannot delay or block the detail response.
🪄 Autofix

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: e638561a-f766-457d-a622-b01faa4865e5

📥 Commits

Reviewing files that changed from the base of the PR and between b43caaf and 78646d1.

📒 Files selected for processing (14)
  • CHANGELOG.md
  • app/components/ApplicationsList.vue
  • app/composables/useApplicationViews.ts
  • app/pages/dashboard/jobs/[id]/index.vue
  • app/pages/dashboard/jobs/index.vue
  • server/api/applications/[id].get.ts
  • server/api/applications/index.get.ts
  • server/api/jobs/index.get.ts
  • server/database/migrations/0065_application_view.sql
  • server/database/migrations/meta/0065_snapshot.json
  • server/database/migrations/meta/_journal.json
  • server/database/schema/app.ts
  • server/utils/applicationViews.ts
  • tests/unit/application-views.test.ts

Comment on lines +420 to +427
// Opening the drawer renders <ApplicationDetail>, whose detail request logs the
// view server-side; mirroring it here drops the marker straight away.
const { markViewedLocally, isViewed } = useApplicationViews()

function openApplication(applicationId: string) {
selectedApplicationId.value = applicationId
markViewedLocally(applicationId)
}

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 | 🟡 Minor | ⚡ Quick win

Mark deep-linked applications as viewed locally.

When initialApplicationId is set, line 418 assigns the ID directly to selectedApplicationId. It does not call markViewedLocally. The opened application's existing list row can keep its unread indicator until a refetch.

Proposed fix
 const { markViewedLocally, isViewed } = useApplicationViews()
+if (selectedApplicationId.value) markViewedLocally(selectedApplicationId.value)

 function openApplication(applicationId: string) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Opening the drawer renders <ApplicationDetail>, whose detail request logs the
// view server-side; mirroring it here drops the marker straight away.
const { markViewedLocally, isViewed } = useApplicationViews()
function openApplication(applicationId: string) {
selectedApplicationId.value = applicationId
markViewedLocally(applicationId)
}
// Opening the drawer renders <ApplicationDetail>, whose detail request logs the
// view server-side; mirroring it here drops the marker straight away.
const { markViewedLocally, isViewed } = useApplicationViews()
if (selectedApplicationId.value) markViewedLocally(selectedApplicationId.value)
function openApplication(applicationId: string) {
selectedApplicationId.value = applicationId
markViewedLocally(applicationId)
}
🤖 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 `@app/components/ApplicationsList.vue` around lines 420 - 427, Update the
initialApplicationId handling near selectedApplicationId so it also calls
markViewedLocally for the deep-linked application, matching the behavior of
openApplication. Keep the existing direct selection behavior and
useApplicationViews integration unchanged for normal application openings.

Comment on lines +70 to +74
await recordApplicationView({
organizationId: orgId,
applicationId: result.id,
userId: session.user.id,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not block the detail response on the receipt write.

await recordApplicationView(...) keeps the application-detail request pending until the database write completes or fails. A slow database can therefore prevent a recruiter from opening an application.

Schedule this non-critical write after the response, or use a bounded background task mechanism that preserves the receipt attempt without extending request latency.

🤖 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/applications/`[id].get.ts around lines 70 - 74, Update the
application-detail handler around recordApplicationView so the non-critical
receipt write is scheduled without awaiting it during response construction.
Preserve the receipt attempt while ensuring database latency or failure cannot
delay or block the detail response.

@JoachimLK JoachimLK changed the title Track application read receipts per user feat: Track application read receipts per user Aug 9, 2026
- Enhance workflow to support custom environment name prefixes.
- Add configuration validation and explicit debugging for missing envs.
- Force a redeploy when a new domain is created so the app recognizes
  it.
- Update `isStaleInheritedPreviewUrl` to use `RAILWAY_GIT_PR_NUMBER` for
  more reliable detection in branched environments.
@railway-app
railway-app Bot temporarily deployed to applirank / reqcore-pr-266 August 9, 2026 08:50 Destroyed
Perform the link operation during the retry loop to confirm environment
accessibility and eliminate the redundant link step later in the
workflow.
@railway-app
railway-app Bot temporarily deployed to applirank / reqcore-pr-266 August 9, 2026 12:01 Destroyed

@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: 1

🤖 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 @.github/workflows/railway-pr-domain.yml:
- Around line 66-67: Update the “Install Railway CLI” workflow step to install
an exact pinned `@railway/cli` version instead of the unversioned package, using
the specified version or another explicitly reviewed version.
🪄 Autofix

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: 07f4701f-f701-4c10-868b-8d4cde818027

📥 Commits

Reviewing files that changed from the base of the PR and between 78646d1 and 8d9412f.

📒 Files selected for processing (3)
  • .github/workflows/railway-pr-domain.yml
  • server/utils/auth.ts
  • tests/unit/preview-auth-url.test.ts

Comment thread .github/workflows/railway-pr-domain.yml Outdated
@railway-app
railway-app Bot temporarily deployed to applirank / reqcore-pr-266 August 9, 2026 12:08 Destroyed
Railway clones the production service's Railway-generated domain into each
new PR environment, so creating one from CI is unnecessary. Document the
requirement that production keep a service domain, not just custom domains.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to applirank / reqcore-pr-266 August 9, 2026 15:05 Destroyed
The receipt is written from GET /api/applications/:id, so demo-guard never
sees it and the read-only demo would accumulate rows. It also broke the
demo: every visitor shares demo@reqcore.com, so one visitor opening a
candidate marked it read for everyone after them.

Skip recording for the demo org, silently — the visitor asked for nothing,
so a read-only error would be noise. A follow-up migration clears the rows
0065's backfill already wrote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to applirank / reqcore-pr-266 August 9, 2026 15:48 Destroyed
@JoachimLK
JoachimLK merged commit 0122023 into main Aug 9, 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