Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
157 commits
Select commit Hold shift + click to select a range
a000976
feat(client): switch GraphQL codegen to client-preset
wreality Mar 20, 2026
f00980d
feat(client): colocate avatar fragments in AvatarImage and AvatarBlock
wreality Mar 20, 2026
aac7cc5
feat(backend): add models, builders, and GraphQL types for admin tables
wreality Mar 19, 2026
61981ac
feat(client): add QueryTable component and pagination composables
wreality Mar 19, 2026
e9a194c
chore(client): update GraphQL schema and client dependencies
wreality Mar 19, 2026
a8dc5be
feat(admin): add user administration pages
wreality Mar 19, 2026
9d7c78f
feat(admin): add user submissions page with filters
wreality Mar 19, 2026
6edba44
feat(admin): improve publications admin page
wreality Mar 19, 2026
1b825be
feat(admin): add admin dashboard and improve navigation
wreality Mar 19, 2026
5dc8558
chore(client): update yarn.lock with missing @types/validator entry
wreality Mar 20, 2026
3018690
fix(admin): rename Users link label and remove unused components
wreality Mar 20, 2026
f746823
fix: remove unused useQuasar import and handle null in Publication na…
wreality Mar 20, 2026
104162c
fix(backend): update tests for renamed GraphQL arguments and new sche…
wreality Mar 20, 2026
c61a1ff
Merge remote-tracking branch 'origin/master' into feature/admin-tables
wreality Mar 21, 2026
34e6e66
Merge remote-tracking branch 'origin/master' into feature/admin-tables
wreality Mar 25, 2026
ad7ab4b
fix(client): update yarn.lock for new deps and fix test import
wreality Mar 25, 2026
7d3e75b
fix(backend): resolve phpcs lint violations
wreality Mar 25, 2026
7b56077
Merge remote-tracking branch 'origin/master' into feature/admin-tables
wreality Mar 26, 2026
01bae36
Merge remote-tracking branch 'origin/master' into feature/admin-tables
wreality Apr 18, 2026
bf00fc8
feat(tables): backport QueryTable refinements and add hideUsername
wreality Apr 18, 2026
d76c1a5
feat: add publication dashboard with submission status overview
wreality Apr 13, 2026
d9e8b2c
feat(dashboard): expandable avatar list, empty states, apollo cache fix
wreality Apr 14, 2026
5e9bdd0
refactor(dashboard): align card header with table column header sizing
wreality Apr 14, 2026
fb680b8
feat(dashboard): expand-all reviewers, top-aligned cells, consistent …
wreality Apr 15, 2026
958e654
feat(dashboard): search with prefixed user queries and syntax hint
wreality Apr 16, 2026
4ef9290
fix(dashboard): align all table cells to the same vertical baseline
wreality Apr 16, 2026
cd1ea18
feat(dashboard): search refinements - user prefix, wildcard escape, m…
wreality Apr 16, 2026
3fd1d67
feat(dashboard): click status badge to change submission status
wreality Apr 16, 2026
1154603
refactor(dashboard): move EXPIRED to Needs Action, drop from Completed
wreality Apr 16, 2026
cd02bfa
feat(dashboard): hide drafts from publication dashboard
wreality Apr 16, 2026
8129c97
refactor(dashboard): title as link, remove row-click navigation
wreality Apr 16, 2026
7f064a3
chore(seeders): add dashboard demo publication with realistic data
wreality Apr 16, 2026
6f4245b
feat(dashboard): responsive layout for smaller screen sizes
wreality Apr 16, 2026
e3c89f7
feat(dashboard): grid card layout and responsive refinements
wreality Apr 16, 2026
09b3226
refactor(dashboard): consolidate coordinator and reviewers into revie…
wreality Apr 17, 2026
faeb574
chore(backend): isolate tests on a separate testing database
wreality Apr 17, 2026
31c2577
test(backend): cover publication dashboard search and authorization
wreality Apr 17, 2026
deae093
test(client): add vitest specs for dashboard logic modules
wreality Apr 17, 2026
491ca23
style(review-team): add gutter between reviewer avatars in collapsed …
wreality Apr 17, 2026
af63070
chore(seeders): share submitter, coordinator, and reviewer pool
wreality Apr 17, 2026
e4b44d1
chore(client): remove unused vi import from dashboard filter spec
wreality Apr 17, 2026
6fb9855
feat(dashboard): add team: search prefix for reviewers and coordinators
wreality Apr 17, 2026
d0f0ab5
fix(router): preserve scroll position on same-route navigation
wreality Apr 18, 2026
3a1297b
style(avatar-cells): smaller avatars and tighter label spacing
wreality Apr 18, 2026
c94d756
refactor(submission-card): restructure header and add submitted time
wreality Apr 18, 2026
a51ca03
feat(dashboard): grid view toggle, responsive cards, tinted grid
wreality Apr 18, 2026
b7e4b62
feat(review-team): RC badge and persistent unassigned placeholder
wreality Apr 18, 2026
ba8a562
chore(seeders): seed unassigned-coordinator and unassigned-reviewer c…
wreality Apr 18, 2026
c9ce88a
chore(backend): phpcs fix for new sniffer rules post-rebase
wreality Apr 18, 2026
71d87b4
feat(publication-admin): users management with submitters, team, and …
wreality Apr 18, 2026
d8ee515
feat(publication-admin): link dashboard to users and reroute users br…
wreality Apr 18, 2026
ca97701
refactor(publication-admin): flatten manage routes and reserve users/…
wreality Apr 18, 2026
50c9a7e
feat(client): adopt vue-router file-based routing + centralized bread…
wreality Apr 19, 2026
5eeb6f8
refactor(publication-admin): give dashboard its own URL to avoid inde…
wreality Apr 19, 2026
33e103f
feat(publication-admin): user detail page + publication.user query
wreality Apr 19, 2026
312af1f
feat(publication-admin): split user detail into submitter and team-me…
wreality Apr 19, 2026
aa3072e
feat(breadcrumbs): allow a route to contribute multiple crumbs
wreality Apr 19, 2026
dda13b5
refactor(publication-admin): submitters tab moves onto the dashboard
wreality Apr 19, 2026
fdaf6c6
feat(dashboard): show per-status counts under each category card
wreality Apr 19, 2026
7239247
feat(publication-admin): grid view for submitters and review team
wreality Apr 19, 2026
55dd71e
feat(publication-admin): sort menu in grid mode for submitters and re…
wreality Apr 19, 2026
3f554a2
feat(publication-admin): show submission icons per submitter up to a …
wreality Apr 19, 2026
2a07e12
feat(publication-admin): nested paginator on PublicationUser.submissi…
wreality Apr 19, 2026
59873e4
refactor(submission-card): move status category to a corner overlay, …
wreality Apr 19, 2026
7310c36
style(submission-card): use a q-badge for the category indicator
wreality Apr 19, 2026
eec992b
style(submission-card): inline category icon next to the title at rea…
wreality Apr 19, 2026
31e3019
refactor(submission-card): use horizontal q-card-sections for the hea…
wreality Apr 19, 2026
a74c7bf
chore(seeder): add long-title submissions for UI stress testing
wreality Apr 19, 2026
dcf1a22
style(submission-card): clamp title to 3 lines and fix status chip co…
wreality Apr 19, 2026
a0fa964
fix(submission-card): drop `block` utility so line-clamp takes effect
wreality Apr 19, 2026
d1e42a3
feat(publication-admin): show submission icons + "+N" overflow chip, …
wreality Apr 19, 2026
6934209
feat(dashboard): add lifecycle-ordered status flow above the category…
wreality Apr 19, 2026
60d19a8
feat(dashboard): turn status flow into a three-lane workflow diagram
wreality Apr 19, 2026
257b1e1
refactor(dashboard): combine workflow lanes into a single panel aroun…
wreality Apr 19, 2026
f62982d
style(dashboard): swap with-author lane so resubmission_requested sit…
wreality Apr 19, 2026
dc26023
style(dashboard): drop resubmitted from the active lane so initially_…
wreality Apr 19, 2026
0e26e55
style(dashboard): put resubmitted above initially_submitted in the wi…
wreality Apr 19, 2026
1f19d1b
feat(dashboard): stack RESUBMITTED and INITIALLY_SUBMITTED as the act…
wreality Apr 19, 2026
831b93c
style(dashboard): stack awaiting_review / under_review / awaiting_dec…
wreality Apr 19, 2026
586245b
style(dashboard): add revision_requested/accepted_as_final as the act…
wreality Apr 19, 2026
ac191e6
style(dashboard): tuck resubmission_requested into the bottom of the …
wreality Apr 19, 2026
48bb014
style(status-categories): move AWAITING_REVIEW to needs_action and EX…
wreality Apr 19, 2026
0ca55c9
style(dashboard): split awaiting_review into its own column between i…
wreality Apr 19, 2026
48fb46e
style(dashboard): move resubmission_requested one column left (under …
wreality Apr 19, 2026
3953317
feat(dashboard): label each active-pipeline column with its stage
wreality Apr 19, 2026
4d0d391
style(dashboard): clean up the workflow diagram layout
wreality Apr 19, 2026
e7f20c0
style(dashboard): drop the accented container around the active pipel…
wreality Apr 19, 2026
df128ac
feat(dashboard): combine initially_submitted + resubmitted into one i…
wreality Apr 19, 2026
b5983a2
style(dashboard): rename stage labels — intake -> screening, screenin…
wreality Apr 19, 2026
c0c134c
style(dashboard): label the combined intake chip "Submitted" and show…
wreality Apr 19, 2026
441b9ae
refactor(dashboard): move resubmission_requested into the screening c…
wreality Apr 19, 2026
4e5d0d5
style(dashboard): collapse assignment column back into reviewing — aw…
wreality Apr 19, 2026
7b3788a
style(dashboard): move revision_requested into the reviewing column a…
wreality Apr 19, 2026
e21d834
style(dashboard): awaiting_decision is the decision column; accepted_…
wreality Apr 19, 2026
a7b6df8
style(dashboard): per-column vertical alignment so the happy path rea…
wreality Apr 19, 2026
08f9f1e
feat(api): per-user submission_status_counts + optional status filter
wreality Apr 20, 2026
491e2bd
feat(dashboard): interactive workflow diagram with clickable filters
wreality Apr 20, 2026
50dd32f
feat(submissions): filter chip in header, URL-synced, with 2-line tit…
wreality Apr 20, 2026
3aff707
feat(detail): QueryTable + clickable status-breakdown on user detail …
wreality Apr 20, 2026
d29ffde
chore: narrow screencast gitignore, loosen HMR allowedHosts
wreality Apr 20, 2026
aa72d15
feat(tables): clickable user cells link to per-user detail pages
wreality Apr 20, 2026
c9ed4be
feat(search): id: prefix for exact submission id match
wreality Apr 21, 2026
0df46b4
feat(seeders): populate profile_metadata on seeded users
wreality Apr 21, 2026
fc3c424
feat(user-detail): submission status bar and profile card
wreality Apr 21, 2026
a498e20
refactor(team-tab): 2x2 role x phase matrix in grid view
wreality Apr 21, 2026
6f36e1a
refactor(tables): clickable name/review team cells and caption-above
wreality Apr 21, 2026
d136f5c
refactor(submission-card): stacked icon strip header
wreality Apr 21, 2026
c5bd918
style(dashboard): reindent diagram after 8-4 grid wrapper
wreality Apr 21, 2026
f3d5408
fix(dashboard): restore light-mode grid tint + id caption above titles
wreality Apr 21, 2026
0a6765c
chore(home): remove redundant Users button from publication home
wreality Apr 21, 2026
8fb1ac7
chore(client): register Atkinson bold and italic font faces
wreality Apr 21, 2026
7f7e504
style(dashboard): 2-up chips inside each stage column on phone width
wreality Apr 21, 2026
13c86aa
style(dashboard): tighten vertical rhythm on phone width
wreality Apr 21, 2026
c3ca60b
fix(tables): expose result from usePaginatedQuery
wreality Apr 21, 2026
21cad8f
fix(security): gate admin user queries and scope user-scoped fields
wreality Apr 21, 2026
27a3e29
fix(dashboard): center the solitary category card on phone width
wreality Apr 21, 2026
e62eea0
refactor(client): consolidate grid-view tint + drop submitter count card
wreality Apr 21, 2026
883b8e3
refactor(status-bar): drop bar on expanded closed; clickable legend rows
wreality Apr 21, 2026
2d3eed6
refactor(submission-card): status chip in title row, submitter as its…
wreality Apr 21, 2026
a1ec504
refactor(team-detail): static "Detail" breadcrumb label
wreality Apr 21, 2026
e1de68e
feat(backend): publications query accepts an array of my_role values
wreality Apr 22, 2026
3e92e39
fix(backend): guard admin-facing Publication nested fields
wreality Apr 22, 2026
a1a15ac
feat(seeders): supplemental publications for the editor with varied m…
wreality Apr 22, 2026
a3373e3
feat(manage): landing page listing publications you administer
wreality Apr 22, 2026
ab7c4d0
feat(admin): Dashboard button on the admin publications list
wreality Apr 22, 2026
c31714d
Merge remote-tracking branch 'origin/master' into feature/admin-tables
wreality Apr 22, 2026
801bad7
Merge remote-tracking branch 'origin/feature/admin-tables' into featu…
wreality Apr 22, 2026
5b79d78
fix(seeders): run ExportPreviewSeeder before DashboardDemoSeeder
wreality Apr 23, 2026
270f039
feat(dashboard): banner pointing managers to the new /manage UI
wreality Apr 23, 2026
348eed3
style(manage): suppress breadcrumb on manage index to avoid duplicate…
wreality Apr 23, 2026
a2533a3
feat(manage): friendly empty state on the manage index
wreality Apr 23, 2026
d33d84b
fix(backend): group visible() OR disjunction so filters compose
wreality Apr 23, 2026
1593d0f
fix(graphiql): read XSRF token fresh from cookie on every request
wreality Apr 23, 2026
ce2a472
refactor(manage): source /manage from User.publications
wreality Apr 23, 2026
5c7acf0
fix(client): resolve vue-tsc errors on SubmissionExport route types
wreality Apr 23, 2026
0fc4472
feat(backend): with_statuses filter on User.publications
wreality Apr 23, 2026
97c3f9d
feat(manage, dashboard): status-aware management UX pass
wreality Apr 23, 2026
c6af488
Merge remote-tracking branch 'origin/master' into feature/admin-tables
wreality Apr 24, 2026
6ba45c8
fix(admin): honor arg in PublicationBuilder public/accepting scopes
wreality Apr 24, 2026
d0bc8fb
refactor(admin): adopt publication-admin filter panel convention
wreality Apr 24, 2026
0d3890f
feat(client): wire file-based routing + shared breadcrumbs
wreality Apr 24, 2026
fa62419
refactor(admin): migrate admin pages to src/routes/admin
wreality Apr 24, 2026
8dc4b24
feat(backend): harden publication search + add FULLTEXT index
wreality Apr 24, 2026
d76f4a6
refactor(manage, dashboard): readability polish on count chips + tabl…
wreality Apr 24, 2026
9f7d589
Merge remote-tracking branch 'origin/feature/admin-tables' into featu…
wreality Apr 24, 2026
a60f53e
feat(manage): submission detail page + review team picker
wreality Apr 28, 2026
6d1cb18
refactor(manage): submission detail tab consistency + overview panel
wreality Apr 29, 2026
bf86757
fix(submission): resolve submitted_at from audit log + safe non-auth …
wreality Apr 29, 2026
fef223a
feat(account): user settings — preferences, dismissals, lab features
wreality Apr 30, 2026
9d10b79
refactor(account): rename color_blind_patterns to a11y_color_patterns
wreality Apr 30, 2026
fb8fff9
fix(account): toggle correct body class for accessible patterns
wreality Apr 30, 2026
fc0b151
fix: remove the uneeded summary for the dissmissable settings UI
wreality Apr 30, 2026
86d86d3
feat(dashboard): update manage banner CTA and adjust submissions tabl…
wreality Apr 30, 2026
7659c73
feat: add preview images to labs page
wreality Apr 30, 2026
11061a0
style: fix lint errors
wreality Apr 30, 2026
1d156dd
fix: failing tests
wreality Apr 30, 2026
3a02ccb
style: setting page headers fix
wreality Apr 30, 2026
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Thumbs.db

# Tool specific files #
#######################
# Root-level one-off screencast scratch (not part of the main test setup).
# Limited to the repo root so nested `screencast/` folders (e.g. playwright
# specs) are still tracked.
/screencast/
# PHPUnit
.phpunit.result.cache
clover.xml
Expand Down
7 changes: 1 addition & 6 deletions backend/.env.testing
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ APP_NAME="Pilcrow Testing"
APP_DEBUG=true
APP_URL=https://localhost/

DB_CONNECTION=mysql
DB_DATABASE=pilcrow
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=pilcrow
DB_CONNECTION=testing
LIGHTHOUSE_CACHE_ENABLE=true
LIGHTHOUSE_CACHE_STORE=file

Expand Down
107 changes: 107 additions & 0 deletions backend/app/Builders/PublicationAssignmentBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);

namespace App\Builders;

use App\Models\Publication;
use Illuminate\Database\Eloquent\Builder;

class PublicationAssignmentBuilder extends Builder
{
/**
* Filter assignments by role.
*
* @param array|null $roles
* @return self
*/
public function roleFilter(?array $roles): self
{
if ($roles) {
$this->whereIn('role_id', $roles);
}

return $this;
}

/**
* Search assignments by the related publication's name.
*
* Delegates to PublicationBuilder::search so the min-length
* guard + LIKE-wildcard escape rules live in one place.
*
* @param string|null $search
* @return self
*/
public function search(?string $search): self
{
if (!is_string($search)) {
return $this;
}
$term = trim($search);
if (mb_strlen($term) < Publication::MIN_SEARCH_LENGTH) {
return $this;
}

return $this->whereHas(
'publication',
fn(Builder $query) => $query->search($term)
);
}

/**
* Restrict to assignments whose publication currently has at least
* one non-draft submission in any of the given statuses. Used by
* /manage to show only publications with activity in a given
* workflow stage. Drafts are always excluded to match the
* dashboard's status-count semantics.
*
* @param array|null $statuses
* @return self
*/
public function withStatuses(?array $statuses): self
{
if ($statuses) {
$this->whereHas('publication.submissions', function (Builder $query) use ($statuses) {
$query
->whereIn('status', $statuses)
->where('status', '!=', 'DRAFT');
});
}

return $this;
}

/**
* Order assignments by a column on the related publication.
*
* @param array|null $orderBy Array of {column, order} pairs
* @return self
*/
public function orderByPublication(?array $orderBy): self
{
if (!$orderBy) {
return $this;
}

$columnMap = [
'NAME' => 'name',
'CREATED_AT' => 'created_at',
'UPDATED_AT' => 'updated_at',
];

foreach ($orderBy as $clause) {
$column = $columnMap[$clause['column']] ?? null;
$direction = $clause['order'] ?? 'ASC';
if ($column) {
$this->orderBy(
Publication::select($column)
->whereColumn('publications.id', 'publication_user.publication_id')
->limit(1),
$direction
);
}
}

return $this;
}
}
95 changes: 95 additions & 0 deletions backend/app/Builders/PublicationBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);

namespace App\Builders;

use App\Models\Publication;
use App\Models\Role;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;

class PublicationBuilder extends Builder
{
/**
* Scope by public visibility. Accepts a boolean so callers can filter
* to hidden publications too (@scope passes the field arg through).
*
* @param bool $value
* @return self
*/
public function public(bool $value = true): self
{
return $this->where('is_publicly_visible', $value);
}

/**
* Scope by accepting-submissions state. Accepts a boolean so callers
* can filter to closed publications too (@scope passes the field arg
* through).
*
* @param bool $value
* @return self
*/
public function acceptingSubmissions(bool $value = true): self
{
return $this->where('is_accepting_submissions', $value);
}

/**
* Add a scope to filter publications by a search string.
*
* Short-circuits on empty / too-short terms so single-keystroke
* searches don't scan the table. Escapes LIKE wildcards so users
* can't (accidentally or otherwise) inject `%` / `_` / `\` into
* the pattern. Matches the hardening applied to the submissions
* search in commit cd1ea18d — the FULLTEXT index on `name` exists
* to support a future MATCH...AGAINST switch.
*
* @param mixed $search
* @return self
*/
public function search(mixed $search): self
{
if (!is_string($search)) {
return $this;
}
$term = trim($search);
if (mb_strlen($term) < Publication::MIN_SEARCH_LENGTH) {
return $this;
}

return $this->where(
'name',
'like',
'%' . addcslashes($term, '%_\\') . '%'
);
}

/**
* Scope to publications visible to the current user.
*
* The public/assigned disjunction is wrapped in a grouped `where`
* so subsequent filters (search, my_role, etc.) AND against the
* whole expression rather than OR-ing around it — otherwise
* public publications leak through regardless of downstream
* filters.
*
* @return self
*/
public function visible(): self
{
$user = Auth::user();

if ($user && $user->hasRole(Role::APPLICATION_ADMINISTRATOR)) {
return $this;
}

return $this->where(function (Builder $query) use ($user) {
$query
->where('is_publicly_visible', true)
->orWhereHas('users', function (Builder $subQuery) use ($user) {
$subQuery->where('user_id', $user->id);
});
});
}
}
111 changes: 111 additions & 0 deletions backend/app/Builders/SubmissionAssignmentBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);

namespace App\Builders;

use App\Models\Submission;
use Illuminate\Database\Eloquent\Builder;

class SubmissionAssignmentBuilder extends Builder
{
/**
* Filter assignments by role.
*
* @param array|null $roles
* @return self
*/
public function roleFilter(?array $roles): self
{
if ($roles) {
$this->whereIn('role_id', $roles);
}

return $this;
}

/**
* Filter assignments by submission status.
*
* @param array|null $status
* @return self
*/
public function statusFilter(?array $status): self
{
if ($status) {
$this->whereHas('submission', function (Builder $query) use ($status) {
$query->whereIn('status', $status);
});
}

return $this;
}

/**
* Filter assignments by publication.
*
* @param array|null $publicationIds
* @return self
*/
public function publicationFilter(?array $publicationIds): self
{
if ($publicationIds) {
$this->whereHas('submission', function (Builder $query) use ($publicationIds) {
$query->whereIn('publication_id', $publicationIds);
});
}

return $this;
}

/**
* Search assignments by submission title.
*
* @param string|null $search
* @return self
*/
public function search(?string $search): self
{
if ($search) {
$this->whereHas('submission', function (Builder $query) use ($search) {
$query->where('title', 'like', "%{$search}%");
});
}

return $this;
}

/**
* Order assignments by a column on the related submission.
*
* @param array|null $orderBy Array of {column, order} pairs
* @return self
*/
public function orderBySubmission(?array $orderBy): self
{
if (!$orderBy) {
return $this;
}

$columnMap = [
'CREATED_AT' => 'created_at',
'UPDATED_AT' => 'updated_at',
'TITLE' => 'title',
'STATUS' => 'status',
];

foreach ($orderBy as $clause) {
$column = $columnMap[$clause['column']] ?? null;
$direction = $clause['order'] ?? 'ASC';
if ($column) {
$this->orderBy(
Submission::select($column)
->whereColumn('submissions.id', 'submission_user.submission_id')
->limit(1),
$direction
);
}
}

return $this;
}
}
Loading
Loading