PHPackages                             rayzenai/project-management - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. rayzenai/project-management

ActiveLibrary

rayzenai/project-management
===========================

Project &amp; task tracking workspace (projects, tasks, subtasks, assignments, notes, contacts) for Laravel + Inertia applications.

1.1.0(1mo ago)064↓66.7%1MITSveltePHP ^8.5

Since Jul 15Pushed 1mo agoCompare

[ Source](https://github.com/rayzenai/project-management)[ Packagist](https://packagist.org/packages/rayzenai/project-management)[ RSS](/packages/rayzenai-project-management/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (6)Versions (3)Used By (1)

rayzenai/project-management
===========================

[](#rayzenaiproject-management)

A project &amp; task tracking **workspace** (projects → tasks → subtasks, assignments, notes, contacts, teams, members, activity log, notifications, weekly digest) for a Laravel + Inertia (Svelte) host application, with a JSON API for mobile clients.

- PHP `^8.5`, Laravel `^12 || ^13`.
- Web/Inertia surface at `/workspace/*` (session auth) and a JSON API at `/api/v1/*` (Sanctum token auth).

Concepts
--------

[](#concepts)

> Projects are private to the teams attached to them; attach a team to grant access; public projects are visible to everyone.

> Creating a project requires at least one team. A user with no teams is shown a team-creation step first (team creation is super-admin-only), then the project form. Team leaders always have a team, so they go straight to the project form.

Installation
------------

[](#installation)

Published on [Packagist](https://packagist.org/packages/rayzenai/project-management):

```
composer require rayzenai/project-management
php artisan migrate
```

### Local development against a host app

[](#local-development-against-a-host-app)

This package lives at `~/packages/project-management`, sibling to the Laravel host apps under `~/rayzen`. While co-developing the package alongside a host, point the host at the local checkout instead of Packagist via a relative path repository with `symlink: true` (adjust the relative path depth to wherever the host repo sits):

```
"repositories": [
    { "type": "path", "url": "../../packages/project-management", "options": { "symlink": true } }
],
"require": {
    "rayzenai/project-management": "@dev"
}
```

Edits under `~/packages/project-management` are then live in the host immediately — no `composer update` needed. Bump the version and tag a release (see below) once changes are ready to ship to other consumers.

The service provider is auto-discovered. It registers the migrations, the `/workspace` (web) and `/api/v1` (API) routes, the `manage-workspace` gate, the model observers, the morph map, and the scheduled commands. Optionally publish the config:

```
php artisan vendor:publish --tag=project-management-config
```

Set your super-admins and (optionally) the user model in `.env` / the published config:

```
PM_SUPER_ADMINS="you@example.com"
```

Host integration (manual steps)
-------------------------------

[](#host-integration-manual-steps)

A few things live in the **host app** because the package can't reach into your bootstrap, Inertia, or Vite config. Wire these up once.

### 1. Guest redirect → the branded workspace login ⚠️ required

[](#1-guest-redirect--the-branded-workspace-login--️-required)

The workspace ships its own branded login page at **`GET /workspace/login`**(route name `workspace.login`). Unauthenticated visitors to any `/workspace/*`route must be sent there. In `bootstrap/app.php`, make the guest redirect path-aware so the workspace uses its login while the rest of your app (e.g. a Filament admin panel) keeps its own:

```
use Illuminate\Http\Request;

->withMiddleware(function (Middleware $middleware) {
    $middleware->redirectGuestsTo(fn (Request $request): string =>
        $request->is('workspace', 'workspace/*')
            ? route('workspace.login')
            : '/admin/login'); // your app's own login for everything else
})
```

If your app has no other authenticated area, you can simply use `$middleware->redirectGuestsTo(fn () => route('workspace.login'));`.

After logout (`POST /workspace/logout`) the user is returned to this same login page via a full-page Inertia location visit.

### 2. Inertia root view + Vite entry for the workspace SPA

[](#2-inertia-root-view--vite-entry-for-the-workspace-spa)

The workspace renders through its **own** Inertia app — a separate JS bundle and a separate HTML shell from your host's own pages. Every Inertia page needs an HTML document to mount into, so the workspace ships **two host files** you must create:

- `resources/views/workspace.blade.php` — the root Blade view (the `@inertia`shell that loads the workspace bundle).
- `resources/js/workspace/app.ts` — the Inertia + Svelte entry whose `resolve()`globs the package's `Pages/`.

**Publish them (recommended):**

```
php artisan vendor:publish --tag=project-management-host
```

This drops both files into place. Prefer to **copy manually**? Grab them from `vendor/rayzenai/project-management/resources/stubs/workspace.blade.php` and `…/resources/stubs/workspace-app.ts` and place them at the two paths above.

> **Adjust the package path inside both files.** The stubs reference `vendor/rayzenai/project-management/…` (a normal `composer require` install). If you consume the package via a **path repository / monorepo** (as this repo does), change those references to `packages/project-management/…`. The `@vite([...])`path in the Blade and the two `import`/`import.meta.glob` paths in `app.ts` must all point at wherever the package actually lives, and must match your Vite input.

Then wire the two things the package **can't** publish (they edit existing files):

`vite.config.*` — add both inputs (match the path you used above):

```
input: [
    /* …your host inputs… */
    'vendor/rayzenai/project-management/resources/js/styles/workspace.css',
    'resources/js/workspace/app.ts',
],
```

`HandleInertiaRequests` — return the workspace root view for `/workspace` paths:

```
public function rootView(Request $request): string
{
    return $request->is('workspace', 'workspace/*') ? 'workspace' : $this->rootView;
}
```

Without `workspace.blade.php` every `/workspace/*` route (including `/workspace/login`) throws *“View \[workspace\] not found”* — it is the load-bearing HTML shell for the whole workspace SPA, not an optional file.

### 3. API auth (mobile clients)

[](#3-api-auth-mobile-clients)

The JSON API uses Sanctum bearer tokens. No extra host wiring beyond having Sanctum installed and a `personal_access_tokens` table migrated.

HTTP API reference
------------------

[](#http-api-reference)

The package exposes the workspace through **two HTTP surfaces that share the same services, FormRequest authorization, and JsonResources** — only the response envelope differs:

- **JSON API** — prefix `api/v1`, route names `api.*`, **Sanctum bearer** auth. Built for mobile clients (the Flutter app in `../task-management`).
- **Web (Inertia)** — prefix `workspace`, route names `workspace.*`, **session**auth (`web` guard). Returns Inertia redirects carrying a `workspace_flash`instead of JSON.

### Conventions (JSON API)

[](#conventions-json-api)

- **Base:** `${APP_URL}/api/v1`. **Auth:** `Authorization: Bearer ` on everything except `POST /login`. Send `Accept: application/json`.
- **Success:** `{ "message": string, "data":  }` — `200`, or `201` on create.
- **Error:** `{ "message": string, "errors": { field: message } }` — `401`unauthenticated, `403` forbidden, `404` not found, `422` validation.
- **Authorization** is enforced server-side via `WorkspaceAccess` (three tiers: super-admin → team-leader → member). Clients mirror role gating in the UI only.
- **Project visibility:** a project is visible to a user iff `is_public` OR the user's member is in a team attached to the project OR the user is a super-admin. Implemented as `Project::scopeVisibleTo($user)` and enforced on the project index (web + API), project/task `show` (403 otherwise), dashboard, My Workspace, search, and the quick-add picker. Relevant `WorkspaceAccess` gates:
    - `canViewProject($user, $project)` — the visibility rule above (drives the `show` 403).
    - `canCreateProject($user)` — super-admin, or a user who leads ≥1 team.
    - `canManageProjectAccess($user, $project)` — super-admin, or a user who leads a team attached to the project. Gates project update/access changes; `canArchiveProject` delegates to it.
- **Soft delete + undo:** every `DELETE` soft-deletes; each resource has a matching `…/restore` (authorization for restore equals authorization for the delete). Trashed rows are pruned after `trash_ttl_days` (default 30).
- **Timestamps** are ISO-8601 (UTC). Projects and tasks are **slug-routed**.

### Account &amp; session

[](#account--session)

MethodPathBodyReturnsPOST`/login``email`, `password`, `device_name``{ token, user }`POST`/logout`—`{ message }` (revokes the current token)GET`/user`—the caller's workspace context: `{ id, name, email, member, is_super_admin, led_team_ids }`The **web** surface logs in via a branded page instead (see *Auth surfaces*below).

### Notifications (in-app inbox)

[](#notifications-in-app-inbox)

Top-level under `/api/v1` (not the `workspace` prefix). In-app only — email/push are deferred.

MethodPathReturnsGET`/notifications?page=`paginated `[Notification]`, newest first (`data` + `meta` + `links`)GET`/notifications/unread-count``{ data: { count } }`POST`/notifications/{id}/read``{ message }` (sets `read_at`)POST`/notifications/read-all``{ message }`### Feeds &amp; overview (`/workspace/*`)

[](#feeds--overview--workspace)

MethodPathNotesGET`/workspace/dashboard`per-project rollups, status breakdown, recent activityGET`/workspace/my`the caller's focused tasks, open todos, assigned workGET`/workspace/plan-tracker`the 100-point plan trackerGET`/workspace/search?q=`task search across active projectsPOST`/workspace/quick-add``{ text }` natural-language task capture### Projects

[](#projects)

MethodPathNotesGET`/workspace/projects`list — **visibility-scoped** (only projects visible to the caller; see *Concepts* + *Conventions*). `?archived=1` for archivedPOST`/workspace/projects`create. Body accepts `team_ids: int[]` (required &amp; non-empty **unless** `is_public`; non-super-admins may only attach teams they lead) and `is_public: bool` (**super-admin only** — silently ignored from others). The web UI presents a team-creation step first when the caller has no teamsGET`/workspace/projects/{slug}`show (board + tasks) — **403** if the project isn't visible to the callerPATCH`/workspace/projects/{slug}`update. Same `team_ids` / `is_public` rules as createPATCH`/workspace/projects/{slug}/archive`soft-archive (leader of an attached team / super-admin)PATCH`/workspace/projects/{slug}/restore`un-archive### Tasks

[](#tasks)

MethodPathNotesPOST`/workspace/projects/{slug}/tasks`createPOST`/workspace/projects/{slug}/tasks/reorder`persist board orderingGET`/workspace/projects/{slug}/tasks/{taskSlug}`task hub (incl. `comments`)PATCH`/workspace/projects/{slug}/tasks/{taskSlug}`update (status, fields)DELETE`/workspace/projects/{slug}/tasks/{taskSlug}`soft-deletePOST`/workspace/projects/{slug}/tasks/{taskSlug}/restore`undo deleteGET`/workspace/tasks/{task}/preview`lightweight peek (incl. `comments_count`)### Subtasks *(personal to the caller)*

[](#subtasks--personal-to-the-caller)

MethodPathPOST`/workspace/tasks/{task}/subtasks`PATCH`/workspace/subtasks/{subtask}`DELETE`/workspace/subtasks/{subtask}`POST`/workspace/subtasks/{subtask}/restore`### Assignments

[](#assignments)

MethodPathPOST`/workspace/tasks/{task}/assignments`PATCH`/workspace/assignments/{assignment}`DELETE`/workspace/assignments/{assignment}`POST`/workspace/assignments/{assignment}/restore`### Comments + @mentions

[](#comments--mentions)

MethodPathNotesGET`/workspace/tasks/{task}/comments?page=`paginated `[TaskComment]`POST`/workspace/tasks/{task}/comments``{ body }` (201)PATCH`/workspace/comments/{comment}`author-onlyDELETE`/workspace/comments/{comment}`author-only soft-deletePOST`/workspace/comments/{comment}/restore`author-onlyMentions are stored canonically as `@[Display Name](member:ID)`; the resolved `mentions` array on each comment is authoritative.

### Task notes &amp; contacts

[](#task-notes--contacts)

MethodPathPOST`/workspace/tasks/{task}/notes`DELETE`/workspace/notes/{note}`POST`/workspace/notes/{note}/restore`POST`/workspace/tasks/{task}/contacts`### Personal workspace notes *(draggable stickies, owner-only)*

[](#personal-workspace-notes--draggable-stickies-owner-only)

MethodPathNotesGET`/workspace/my-notes`the caller's notes board: `{ data: { workspace_notes, task_notes } }` — own stickies (newest first) plus task notes they authored or that live on a task assigned to them (latest 50). Mirrors what the web shares via Inertia.POST`/workspace/my-notes`createPATCH`/workspace/my-notes/{note}`edit body/colorPATCH`/workspace/my-notes/{note}/placement`move (`position_x/y`)DELETE`/workspace/my-notes/{note}`soft-deletePOST`/workspace/my-notes/{note}/restore`undo### Teams *(roster: leader/super-admin; rename + reassignment: super-admin only)*

[](#teams--roster-leadersuper-admin-rename--reassignment-super-admin-only)

MethodPathGET`/workspace/team`POST`/workspace/teams`PATCH`/workspace/teams/{team}`DELETE`/workspace/teams/{team}`POST`/workspace/teams/{team}/restore`POST`/workspace/teams/{team}/members`DELETE`/workspace/teams/{team}/members/{member}`PATCH`/workspace/teams/{team}/members/{member}` (role)### Members

[](#members)

MethodPathPOST`/workspace/members`PATCH`/workspace/members/{member}`DELETE`/workspace/members/{member}`POST`/workspace/members/{member}/restore`### Appearance, preferences &amp; themes *(host-provided)*

[](#appearance-preferences--themes--host-provided)

These three live in the **host app's** `routes/api.php`(`ThemeController` / `PreferenceController`), not the package, but are part of the workspace contract — the appearance UI and Flutter client consume them. The web app also exposes a session twin at `PATCH /workspace/preferences`.

MethodPathBodyReturnsGET`/themes`—`{ data: { themes: [Theme], font_allow_list } }`GET`/user/preferences`—`{ data: Preferences }`PATCH`/user/preferences``theme?`, `font_override?`, `email_notifications?``{ data: Preferences }`### Resource shapes &amp; the canonical contract

[](#resource-shapes--the-canonical-contract)

This README lists the **endpoint surface**; for full request/response payloads (`Notification`, `TaskComment`, `Preferences`, `Theme`, the mention token format, and the undo/restore client guidance) see the single-source-of-truth contract:

- `docs/api/workspace-api.md` — the versioned JSON API contract.
- `docs/superpowers/specs/2026-06-16-appearance-themes-fonts-design.md` — the full theme token table (the contract for both web `--ws-*` and Flutter `ThemeData`).

Auth surfaces at a glance
-------------------------

[](#auth-surfaces-at-a-glance)

SurfaceLoginLogoutAuthWeb (Inertia)`GET/POST /workspace/login``POST /workspace/logout`session (`web` guard)API (mobile)`POST /api/v1/login``POST /api/v1/logout`Sanctum tokenThe web login page (`/workspace/login`) supports **remember-me**, **show/hide password**, and is **rate limited** (5 attempts per email + IP per minute). The web surface also serves a notifications inbox page (`GET /workspace/notifications`, plus `read`/`read-all` POSTs) and `PATCH /workspace/preferences`; every other web route mirrors the JSON API table above 1:1.

Development
-----------

[](#development)

This package has no test suite of its own — it is exercised by the host app's Pest suite under `tests/Feature/Workspace/`. Run `vendor/bin/pint --dirty` before committing. See `CLAUDE.md` in this directory for the full architecture guide.

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance93

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~11 days

Total

2

Last Release

35d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/351e3224dcfda5f2fc1a86fb6424f60a9a060a39c938c6338a244d01912e077d?d=identicon)[kiran1991](/maintainers/kiran1991)

---

Top Contributors

[![timsinakiran](https://avatars.githubusercontent.com/u/50225225?v=4)](https://github.com/timsinakiran "timsinakiran (134 commits)")

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/rayzenai-project-management/health.svg)

```
[![Health](https://phpackages.com/badges/rayzenai-project-management/health.svg)](https://phpackages.com/packages/rayzenai-project-management)
```

###  Alternatives

[backpack/crud

Quickly build admin interfaces using Laravel, Bootstrap and JavaScript.

3.4k3.8M231](/packages/backpack-crud)[code16/sharp

Laravel Content Management Framework

79466.8k10](/packages/code16-sharp)[statamic-rad-pack/runway

Eloquently manage your database models in Statamic.

138249.0k8](/packages/statamic-rad-pack-runway)[duncanmcclean/statamic-cargo

Comprehensive e-commerce addon for Statamic. Build bespoke e-commerce sites without the complexity.

3622.8k](/packages/duncanmcclean-statamic-cargo)[ecotone/laravel

Ecotone for Laravel — CQRS, Event Sourcing, Sagas, Durable Workflows, and Outbox on top of Laravel Queue, via PHP attributes.

21336.4k4](/packages/ecotone-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
