Compare commits
8
Commits
9895fc36db
..
v0.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ceffe4f4a4 | ||
|
|
ce01b8ec55 | ||
|
|
bd774e20f6 | ||
|
|
d9cb8d0903 | ||
|
|
19e8ba89bf | ||
|
|
642e49d7b0 | ||
|
|
1d8fff1d51 | ||
|
|
bd7be4e41e |
@@ -19,6 +19,7 @@ class CreateProjectTicket
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly BuildTicketCreatedPayload $buildTicketCreatedPayload,
|
private readonly BuildTicketCreatedPayload $buildTicketCreatedPayload,
|
||||||
private readonly CreateTicketWorklogEntry $createTicketWorklogEntry,
|
private readonly CreateTicketWorklogEntry $createTicketWorklogEntry,
|
||||||
|
private readonly SyncProjectTicketFieldValues $syncProjectTicketFieldValues,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,7 +30,15 @@ class CreateProjectTicket
|
|||||||
* text: string
|
* text: string
|
||||||
* }>,
|
* }>,
|
||||||
* assignee_user_id?: int|null,
|
* assignee_user_id?: int|null,
|
||||||
|
* author_user_id?: int|null,
|
||||||
|
* blocked_comment?: string|null,
|
||||||
* description: string|null,
|
* description: string|null,
|
||||||
|
* field_values?: array<int, array{
|
||||||
|
* ticket_field_definition_id: int,
|
||||||
|
* value_number?: float|int|string|null,
|
||||||
|
* value_user_id?: int|string|null
|
||||||
|
* }>,
|
||||||
|
* is_blocked?: bool,
|
||||||
* parent_ticket_id?: int|null,
|
* parent_ticket_id?: int|null,
|
||||||
* story_points?: int|null,
|
* story_points?: int|null,
|
||||||
* ticket_priority_id: int,
|
* ticket_priority_id: int,
|
||||||
@@ -50,11 +59,14 @@ class CreateProjectTicket
|
|||||||
$ticketStatusQuery = TicketStatus::query()->where('project_id', $project->id);
|
$ticketStatusQuery = TicketStatus::query()->where('project_id', $project->id);
|
||||||
|
|
||||||
$ticketStatus = $ticketStatusQuery->findOrFail(Arr::get($attributes, 'ticket_status_id'));
|
$ticketStatus = $ticketStatusQuery->findOrFail(Arr::get($attributes, 'ticket_status_id'));
|
||||||
|
$isBlocked = (bool) Arr::get($attributes, 'is_blocked', false);
|
||||||
|
|
||||||
$ticket = Ticket::query()->create([
|
$ticket = Ticket::query()->create([
|
||||||
'assignee_user_id' => Arr::get($attributes, 'assignee_user_id'),
|
'assignee_user_id' => Arr::get($attributes, 'assignee_user_id'),
|
||||||
'author_user_id' => $actor?->id,
|
'author_user_id' => Arr::get($attributes, 'author_user_id', $actor?->id),
|
||||||
|
'blocked_comment' => $isBlocked ? Arr::get($attributes, 'blocked_comment') : null,
|
||||||
'description' => Arr::get($attributes, 'description'),
|
'description' => Arr::get($attributes, 'description'),
|
||||||
|
'is_blocked' => $isBlocked,
|
||||||
'parent_ticket_id' => Arr::get($attributes, 'parent_ticket_id'),
|
'parent_ticket_id' => Arr::get($attributes, 'parent_ticket_id'),
|
||||||
'project_id' => $project->id,
|
'project_id' => $project->id,
|
||||||
'sequence_number' => $nextSequenceNumber,
|
'sequence_number' => $nextSequenceNumber,
|
||||||
@@ -68,6 +80,17 @@ class CreateProjectTicket
|
|||||||
|
|
||||||
AcceptanceCriteriaChecklistMapper::syncTicket($ticket, $acceptanceCriteria);
|
AcceptanceCriteriaChecklistMapper::syncTicket($ticket, $acceptanceCriteria);
|
||||||
|
|
||||||
|
$fieldValues = Arr::get($attributes, 'field_values', []);
|
||||||
|
|
||||||
|
if ($fieldValues !== []) {
|
||||||
|
$this->syncProjectTicketFieldValues->handle(
|
||||||
|
$ticket,
|
||||||
|
$project->ticketFieldDefinitions()->get()->keyBy('id'),
|
||||||
|
collect(),
|
||||||
|
$fieldValues,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$occurredAt = CarbonImmutable::now();
|
$occurredAt = CarbonImmutable::now();
|
||||||
|
|
||||||
if ($actor !== null) {
|
if ($actor !== null) {
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Mcp\Servers;
|
namespace App\Mcp\Servers;
|
||||||
|
|
||||||
|
use App\Mcp\Tools\CreateTicket;
|
||||||
use App\Mcp\Tools\GetTicket;
|
use App\Mcp\Tools\GetTicket;
|
||||||
use App\Mcp\Tools\ListMyAssignedTickets;
|
use App\Mcp\Tools\ListMyAssignedTickets;
|
||||||
use App\Mcp\Tools\ListTicketsByStatus;
|
use App\Mcp\Tools\ListTicketsByStatus;
|
||||||
|
use App\Mcp\Tools\ListTicketTypes;
|
||||||
use App\Mcp\Tools\MoveTicketStatus;
|
use App\Mcp\Tools\MoveTicketStatus;
|
||||||
use App\Mcp\Tools\UpdateTicketAssignee;
|
use App\Mcp\Tools\UpdateTicketAssignee;
|
||||||
use App\Mcp\Tools\UpdateTicketDescription;
|
use App\Mcp\Tools\UpdateTicketDescription;
|
||||||
@@ -15,7 +17,7 @@ use Laravel\Mcp\Server\Attributes\Version;
|
|||||||
|
|
||||||
#[Name('KanbanGit MCP')]
|
#[Name('KanbanGit MCP')]
|
||||||
#[Version('0.1.0')]
|
#[Version('0.1.0')]
|
||||||
#[Instructions('Use these tools to read and update KanbanGit tickets with the authenticated application key owner permissions. Prefer stable IDs when available, especially ticket_id and ticket_status_id.')]
|
#[Instructions('Use these tools to read, create, and update KanbanGit tickets with the authenticated application key owner permissions. Prefer stable IDs when available, especially project_id, ticket_id, ticket_type_id, and ticket_status_id. Use list_ticket_types to discover the available ticket types in a project. Use create_ticket with parent_ticket_id to create a child ticket.')]
|
||||||
class KanbanGitServer extends Server
|
class KanbanGitServer extends Server
|
||||||
{
|
{
|
||||||
protected array $tools = [
|
protected array $tools = [
|
||||||
@@ -25,6 +27,8 @@ class KanbanGitServer extends Server
|
|||||||
UpdateTicketDescription::class,
|
UpdateTicketDescription::class,
|
||||||
UpdateTicketAssignee::class,
|
UpdateTicketAssignee::class,
|
||||||
MoveTicketStatus::class,
|
MoveTicketStatus::class,
|
||||||
|
CreateTicket::class,
|
||||||
|
ListTicketTypes::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
protected array $resources = [
|
protected array $resources = [
|
||||||
|
|||||||
@@ -120,6 +120,24 @@ class KanbanGitMcp
|
|||||||
return $project;
|
return $project;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws AuthorizationException
|
||||||
|
*/
|
||||||
|
public function projectForWrite(User $user, int $projectId): Project
|
||||||
|
{
|
||||||
|
$project = Project::query()->find($projectId);
|
||||||
|
|
||||||
|
if ($project === null) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'project_id' => 'Project was not found.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->ensureCanWrite($user, $project);
|
||||||
|
|
||||||
|
return $project;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $validated
|
* @param array<string, mixed> $validated
|
||||||
* @return Collection<int, TicketStatus>|Response
|
* @return Collection<int, TicketStatus>|Response
|
||||||
@@ -189,10 +207,12 @@ class KanbanGitMcp
|
|||||||
public function ticketPayload(Ticket $ticket): array
|
public function ticketPayload(Ticket $ticket): array
|
||||||
{
|
{
|
||||||
$ticket->load([
|
$ticket->load([
|
||||||
|
'acceptanceCriteria',
|
||||||
'assignee',
|
'assignee',
|
||||||
'author',
|
'author',
|
||||||
'fieldValues.ticketFieldDefinition',
|
'fieldValues.ticketFieldDefinition',
|
||||||
'fieldValues.userValue',
|
'fieldValues.userValue',
|
||||||
|
'parentTicket',
|
||||||
'project',
|
'project',
|
||||||
'ticketPriority',
|
'ticketPriority',
|
||||||
'ticketStatus.boardColumn',
|
'ticketStatus.boardColumn',
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mcp\Tools;
|
||||||
|
|
||||||
|
use App\Actions\Permissions\PermissionService;
|
||||||
|
use App\Actions\Tickets\CreateProjectTicket;
|
||||||
|
use App\Actions\Tickets\Support\AcceptanceCriteriaChecklistMapper;
|
||||||
|
use App\Enums\Permission;
|
||||||
|
use App\Mcp\Support\KanbanGitMcp;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Ticket;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\JsonSchema\JsonSchema;
|
||||||
|
use Illuminate\JsonSchema\Types\Type;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Laravel\Mcp\Request;
|
||||||
|
use Laravel\Mcp\Response;
|
||||||
|
use Laravel\Mcp\ResponseFactory;
|
||||||
|
use Laravel\Mcp\Server\Attributes\Description;
|
||||||
|
use Laravel\Mcp\Server\Attributes\Name;
|
||||||
|
use Laravel\Mcp\Server\Tool;
|
||||||
|
use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
|
||||||
|
|
||||||
|
#[Name('create_ticket')]
|
||||||
|
#[Description('Create a ticket with all editable ticket fields, including its type, initial status, priority, assignee, author, description, story points, acceptance criteria, blocked state, and custom field values. Set parent_ticket_id to create a child of an existing ticket in the same project whose type allows children. Requires project write permission; overriding the author requires project admin permission. Ticket IDs, keys, sequence numbers, timestamps, and worklog entries are generated automatically.')]
|
||||||
|
#[IsDestructive(false)]
|
||||||
|
class CreateTicket extends Tool
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly KanbanGitMcp $kanbanGitMcp,
|
||||||
|
private readonly CreateProjectTicket $createProjectTicket,
|
||||||
|
private readonly PermissionService $permissionService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(Request $request): Response|ResponseFactory
|
||||||
|
{
|
||||||
|
$projectInput = $request->validate([
|
||||||
|
'project_id' => ['required', 'integer'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $this->kanbanGitMcp->user($request);
|
||||||
|
$project = $this->kanbanGitMcp->projectForWrite($user, (int) Arr::get($projectInput, 'project_id'));
|
||||||
|
$validated = $request->validate([
|
||||||
|
'title' => ['required', 'string', 'max:255'],
|
||||||
|
'description' => ['nullable', 'string'],
|
||||||
|
'ticket_type_id' => ['required', 'integer', Rule::exists('ticket_types', 'id')->where('project_id', $project->id)],
|
||||||
|
'ticket_status_id' => ['required', 'integer', Rule::exists('ticket_statuses', 'id')->where('project_id', $project->id)],
|
||||||
|
'ticket_priority_id' => ['required', 'integer', Rule::exists('ticket_priorities', 'id')->where('project_id', $project->id)],
|
||||||
|
'parent_ticket_id' => ['nullable', 'integer', Rule::exists('tickets', 'id')->where('project_id', $project->id)],
|
||||||
|
'assignee_user_id' => ['nullable', 'integer', Rule::exists('users', 'id')],
|
||||||
|
'author_user_id' => ['nullable', 'integer', Rule::exists('users', 'id')],
|
||||||
|
'story_points' => ['nullable', 'integer', 'min:0'],
|
||||||
|
'is_blocked' => ['sometimes', 'boolean'],
|
||||||
|
'blocked_comment' => ['nullable', 'string'],
|
||||||
|
'acceptance_criteria' => ['nullable', 'array', 'list'],
|
||||||
|
'acceptance_criteria.*' => ['array:text,checked,indent'],
|
||||||
|
'acceptance_criteria.*.text' => ['required', 'string'],
|
||||||
|
'acceptance_criteria.*.checked' => ['sometimes', 'boolean'],
|
||||||
|
'acceptance_criteria.*.indent' => ['sometimes', 'integer', 'min:0', 'max:'.AcceptanceCriteriaChecklistMapper::MAX_INDENT_LEVEL],
|
||||||
|
'field_values' => ['sometimes', 'array', 'list'],
|
||||||
|
'field_values.*' => ['array:ticket_field_definition_id,value_number,value_user_id'],
|
||||||
|
'field_values.*.ticket_field_definition_id' => [
|
||||||
|
'required', 'integer', 'distinct',
|
||||||
|
Rule::exists('ticket_field_definitions', 'id')->where('project_id', $project->id),
|
||||||
|
],
|
||||||
|
'field_values.*.value_number' => ['nullable', 'numeric'],
|
||||||
|
'field_values.*.value_user_id' => ['nullable', 'integer', Rule::exists('users', 'id')],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->validateTicketConstraints($project, $user, $validated);
|
||||||
|
|
||||||
|
$ticket = $this->createProjectTicket->handle($project, $validated, $user);
|
||||||
|
|
||||||
|
return Response::structured([
|
||||||
|
'data' => $this->kanbanGitMcp->ticketPayload($ticket->fresh()),
|
||||||
|
'meta' => [
|
||||||
|
'message' => "{$ticket->ticket_key} was created.",
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $validated
|
||||||
|
*/
|
||||||
|
private function validateTicketConstraints(Project $project, User $user, array $validated): void
|
||||||
|
{
|
||||||
|
$errors = [];
|
||||||
|
$parentTicketId = Arr::get($validated, 'parent_ticket_id');
|
||||||
|
|
||||||
|
if ($parentTicketId !== null) {
|
||||||
|
$parentTicket = Ticket::query()
|
||||||
|
->where('project_id', $project->id)
|
||||||
|
->with('ticketType')
|
||||||
|
->find($parentTicketId);
|
||||||
|
|
||||||
|
if (! $parentTicket?->ticketType?->is_parent) {
|
||||||
|
$errors['parent_ticket_id'] = 'Only tickets with a parent-enabled type can be selected as a parent.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Arr::get($validated, 'is_blocked', false) && blank(Arr::get($validated, 'blocked_comment'))) {
|
||||||
|
$errors['blocked_comment'] = 'Blocked tickets require a blocker comment.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Arr::exists($validated, 'author_user_id')
|
||||||
|
&& (int) Arr::get($validated, 'author_user_id') !== $user->id
|
||||||
|
&& ! $this->permissionService->allows($user, Permission::ProjectAdmin, $project)
|
||||||
|
) {
|
||||||
|
$errors['author_user_id'] = 'Only project admins can change the author.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldValues = Arr::get($validated, 'field_values', []);
|
||||||
|
|
||||||
|
if ($fieldValues !== []) {
|
||||||
|
$fieldDefinitions = $project->ticketFieldDefinitions()->get()->keyBy('id');
|
||||||
|
|
||||||
|
foreach ($fieldValues as $index => $fieldValue) {
|
||||||
|
$fieldDefinition = $fieldDefinitions->get((int) Arr::get($fieldValue, 'ticket_field_definition_id'));
|
||||||
|
$incompatibleValue = $fieldDefinition->field_type === 'user' ? 'value_number' : 'value_user_id';
|
||||||
|
|
||||||
|
if (Arr::get($fieldValue, $incompatibleValue) !== null) {
|
||||||
|
$errors["field_values.{$index}.{$incompatibleValue}"] = 'The value must match the custom field type.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errors !== []) {
|
||||||
|
throw ValidationException::withMessages($errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, Type>
|
||||||
|
*/
|
||||||
|
public function schema(JsonSchema $schema): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'project_id' => $schema->integer()->description('Project ID.')->required(),
|
||||||
|
'title' => $schema->string()->max(255)->description('Ticket title.')->required(),
|
||||||
|
'description' => $schema->string()->nullable()->description('Ticket description.'),
|
||||||
|
'ticket_type_id' => $schema->integer()->description('Ticket type ID belonging to the project. Explicitly selects the type, including types that allow child tickets.')->required(),
|
||||||
|
'ticket_status_id' => $schema->integer()->description('Initial ticket status ID belonging to the project.')->required(),
|
||||||
|
'ticket_priority_id' => $schema->integer()->description('Ticket priority ID belonging to the project.')->required(),
|
||||||
|
'parent_ticket_id' => $schema->integer()->nullable()->description('Existing parent ticket ID in the same project. Its type must allow children. Omit or pass null for a top-level ticket.'),
|
||||||
|
'assignee_user_id' => $schema->integer()->nullable()->description('User ID to assign. Omit or pass null to leave unassigned.'),
|
||||||
|
'author_user_id' => $schema->integer()->nullable()->description('Defaults to the application key owner when omitted. Only project admins may choose another author or pass null for no author.'),
|
||||||
|
'story_points' => $schema->integer()->min(0)->nullable()->description('Non-negative story point estimate.'),
|
||||||
|
'is_blocked' => $schema->boolean()->default(false)->description('Whether the ticket is blocked. Requires a nonblank blocked_comment when true.'),
|
||||||
|
'blocked_comment' => $schema->string()->nullable()->description('Blocker reason. Stored only when is_blocked is true.'),
|
||||||
|
'acceptance_criteria' => $schema->array()->nullable()
|
||||||
|
->items($schema->object([
|
||||||
|
'text' => $schema->string()->description('Checklist item text.')->required(),
|
||||||
|
'checked' => $schema->boolean()->default(false),
|
||||||
|
'indent' => $schema->integer()->min(0)->max(AcceptanceCriteriaChecklistMapper::MAX_INDENT_LEVEL)->default(0),
|
||||||
|
])->withoutAdditionalProperties())
|
||||||
|
->description('Ordered acceptance checklist. Indent 0 is a root item; larger values nest under preceding items.'),
|
||||||
|
'field_values' => $schema->array()
|
||||||
|
->items($schema->object([
|
||||||
|
'ticket_field_definition_id' => $schema->integer()->description('Custom field definition ID belonging to the project. Each definition may appear once.')->required(),
|
||||||
|
'value_number' => $schema->number()->nullable()->description('Value for a numeric field.'),
|
||||||
|
'value_user_id' => $schema->integer()->nullable()->description('User ID for a user field.'),
|
||||||
|
])->withoutAdditionalProperties())
|
||||||
|
->description('Custom field values. Supply the value matching each definition type; omit or pass null for an empty value.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mcp\Tools;
|
||||||
|
|
||||||
|
use App\Http\Resources\ProjectResource;
|
||||||
|
use App\Http\Resources\TicketTypeResource;
|
||||||
|
use App\Mcp\Support\KanbanGitMcp;
|
||||||
|
use Illuminate\Contracts\JsonSchema\JsonSchema;
|
||||||
|
use Illuminate\JsonSchema\Types\Type;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Laravel\Mcp\Request;
|
||||||
|
use Laravel\Mcp\Response;
|
||||||
|
use Laravel\Mcp\ResponseFactory;
|
||||||
|
use Laravel\Mcp\Server\Attributes\Description;
|
||||||
|
use Laravel\Mcp\Server\Attributes\Name;
|
||||||
|
use Laravel\Mcp\Server\Tool;
|
||||||
|
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
|
||||||
|
|
||||||
|
#[Name('list_ticket_types')]
|
||||||
|
#[Description('List all ticket types in a visible project in their configured order, including IDs, names, and is_parent. Use a returned ID as ticket_type_id when creating a ticket. Types with is_parent=true allow tickets of that type to have child tickets; set parent_ticket_id on create_ticket to attach a child to an existing parent ticket.')]
|
||||||
|
#[IsReadOnly]
|
||||||
|
class ListTicketTypes extends Tool
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly KanbanGitMcp $kanbanGitMcp,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(Request $request): Response|ResponseFactory
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'project_id' => ['required', 'integer'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $this->kanbanGitMcp->user($request);
|
||||||
|
$project = $this->kanbanGitMcp->projectForRead($user, (int) Arr::get($validated, 'project_id'));
|
||||||
|
|
||||||
|
return Response::structured([
|
||||||
|
'data' => [
|
||||||
|
'project' => ProjectResource::make($project)->resolve(),
|
||||||
|
'ticket_types' => TicketTypeResource::collection($project->ticketTypes()->get())->resolve(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, Type>
|
||||||
|
*/
|
||||||
|
public function schema(JsonSchema $schema): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'project_id' => $schema->integer()
|
||||||
|
->description('Project ID. Requires permission to view this project.')
|
||||||
|
->required(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ class BoardColumn extends Model
|
|||||||
{
|
{
|
||||||
/** @use HasFactory<BoardColumnFactory> */
|
/** @use HasFactory<BoardColumnFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use SyncsProjectTemplateOwnership;
|
use SyncsProjectTemplateOwnership;
|
||||||
|
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class NotificationAction extends Model
|
|||||||
{
|
{
|
||||||
/** @use HasFactory<NotificationActionFactory> */
|
/** @use HasFactory<NotificationActionFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use SyncsProjectTemplateOwnership;
|
use SyncsProjectTemplateOwnership;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class RepositoryConnection extends Model
|
|||||||
{
|
{
|
||||||
/** @use HasFactory<RepositoryConnectionFactory> */
|
/** @use HasFactory<RepositoryConnectionFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use SyncsProjectTemplateOwnership;
|
use SyncsProjectTemplateOwnership;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class TicketFieldDefinition extends Model
|
|||||||
|
|
||||||
/** @use HasFactory<TicketFieldDefinitionFactory> */
|
/** @use HasFactory<TicketFieldDefinitionFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use SyncsProjectTemplateOwnership;
|
use SyncsProjectTemplateOwnership;
|
||||||
|
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class TicketLinkType extends Model
|
|||||||
{
|
{
|
||||||
/** @use HasFactory<TicketLinkTypeFactory> */
|
/** @use HasFactory<TicketLinkTypeFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use SyncsProjectTemplateOwnership;
|
use SyncsProjectTemplateOwnership;
|
||||||
|
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class TicketStatus extends Model
|
|||||||
{
|
{
|
||||||
/** @use HasFactory<TicketStatusFactory> */
|
/** @use HasFactory<TicketStatusFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use SyncsProjectTemplateOwnership;
|
use SyncsProjectTemplateOwnership;
|
||||||
|
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class TicketType extends Model
|
|||||||
{
|
{
|
||||||
/** @use HasFactory<TicketTypeFactory> */
|
/** @use HasFactory<TicketTypeFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use SyncsProjectTemplateOwnership;
|
use SyncsProjectTemplateOwnership;
|
||||||
|
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# UI Rework 1: Englische Lokalisierungsgrundlage
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: Keine.
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Englische Lokalisierungsgrundlage als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/app.ts](../../resources/js/app.ts); [resources/js/ssr.ts](../../resources/js/ssr.ts); [resources/js/types](../../resources/js/types). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Eine zentrale Übersetzungsstruktur für Vue einführen.
|
||||||
|
2. Englischen Katalog, Interpolation, Pluralformen und Formatierung festlegen.
|
||||||
|
3. Serverseitige Validierungstexte einheitlich Englisch halten.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Neue UI-Texte verwenden Schlüssel.
|
||||||
|
- [ ] Fehlende Schlüssel fallen im Test auf.
|
||||||
|
- [ ] Spätere Sprachen benötigen keine Änderungen an Komponenten.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# UI Rework 10: Persönliches Dashboard mit vorhandenen Daten
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 8](epic-1-issue-8.md), [Issue 11](epic-1-issue-11.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Persönliches Dashboard mit vorhandenen Daten als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/Dashboard.vue](../../resources/js/pages/Dashboard.vue); [app/Actions/Tickets/ListAssignedTicketsByVisibleProject.php](../../app/Actions/Tickets/ListAssignedTicketsByVisibleProject.php); [routes/web.php](../../routes/web.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
Relevanter Stitch-Screen: Persönliches Dashboard — 30b69ed84e5c4bdf8cd3fe26d7a9e803. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Dashboard aus zugewiesenen Tickets und sichtbaren Projekten aufbauen.
|
||||||
|
2. Vorhandene Action über einen Inertia-Controller und Resources anbinden.
|
||||||
|
3. Aus dem Mockup nicht belegbare Kennzahlen und Aktivitätsfeeds weglassen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Keine Platzhalterdaten.
|
||||||
|
- [ ] Keine fremden Tickets.
|
||||||
|
- [ ] Leere und gefüllte Dashboards funktionieren.
|
||||||
|
- [ ] Neue Datenweitergabe ist durch Featuretests abgesichert.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/DashboardTest.php](../../tests/Feature/DashboardTest.php), [tests/Feature/Api/AssignedTicketsApiTest.php](../../tests/Feature/Api/AssignedTicketsApiTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# UI Rework 11: Ticketkarten und wiederverwendbare Ticketlisten
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 3](epic-1-issue-3.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Ticketkarten und wiederverwendbare Ticketlisten als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/components](../../resources/js/components); [resources/js/types/models/ticket-model.ts](../../resources/js/types/models/ticket-model.ts). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
Relevanter Stitch-Screen: Board View — 1a8363ad911a494f92cc4468955ce2f2. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Neue Karten und Listenzeilen mit Ticketkey, Titel, Typ, Priorität, Zuweisung und Blockerzustand entwickeln.
|
||||||
|
2. Darstellung mit und ohne optionale Daten definieren.
|
||||||
|
3. Gemeinsame Props und Ereignisse für Board, Backlog und Dashboard festlegen. Ticketlinks über Wayfinder aufbauen und Fokusführung sowie Touch-Ziele prüfen; die Komponenten dürfen keine eigenen Datenabfragen auslösen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Lange Titel bleiben bedienbar.
|
||||||
|
- [ ] Prioritäten und Status stammen aus Projektdaten.
|
||||||
|
- [ ] Links, Fokus und Touch-Ziele funktionieren.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# UI Rework 12: Boardansicht für Desktop und Mobilgeräte
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 8](epic-1-issue-8.md), [Issue 11](epic-1-issue-11.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Boardansicht für Desktop und Mobilgeräte als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/Board.vue](../../resources/js/pages/projects/Board.vue); [app/Actions/Projects/BuildProjectBoardPayload.php](../../app/Actions/Projects/BuildProjectBoardPayload.php); [app/Http/Resources/BoardColumnResource.php](../../app/Http/Resources/BoardColumnResource.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
Relevanter Stitch-Screen: Board View — 1a8363ad911a494f92cc4468955ce2f2. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Horizontales Board mit konfigurierten Spalten neu bauen.
|
||||||
|
2. Mobil eine Spalte mit Auswahl anbieten.
|
||||||
|
3. Statusunterteilung, Zähler, Leerzustände und vorhandene Statusauswahl darstellen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Beliebige Spaltenanzahl funktioniert.
|
||||||
|
- [ ] Mehrere Status pro Spalte bleiben sichtbar.
|
||||||
|
- [ ] Board und Backlog behalten ihre bestehende fachliche Trennung.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectBoardControllerTest.php](../../tests/Feature/Projects/ProjectBoardControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# UI Rework 13: Drag-and-drop für bestehende Statuswechsel
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 5](epic-1-issue-5.md), [Issue 12](epic-1-issue-12.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Drag-and-drop für bestehende Statuswechsel als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/Board.vue](../../resources/js/pages/projects/Board.vue); [app/Http/Requests/MoveProjectTicketRequest.php](../../app/Http/Requests/MoveProjectTicketRequest.php); [app/Actions/Tickets/MoveProjectTicket.php](../../app/Actions/Tickets/MoveProjectTicket.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
Relevanter Stitch-Screen: Board View — 1a8363ad911a494f92cc4468955ce2f2. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Ziehen per Maus und Touch ergänzen.
|
||||||
|
2. Zulässige Ziele aus Workflowdaten ableiten.
|
||||||
|
3. Bei mehreren Zielstatus Auswahl zeigen.
|
||||||
|
4. Vorhandenen Statusendpunkt über Wayfinder verwenden und Fehler zurückrollen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Unzulässige Ziele sind gesperrt.
|
||||||
|
- [ ] Alternative Tastatur-/Menübedienung bleibt möglich.
|
||||||
|
- [ ] Kein doppeltes Worklog.
|
||||||
|
- [ ] Keine persistierte freie Kartenreihenfolge.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectBoardControllerTest.php](../../tests/Feature/Projects/ProjectBoardControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 14: Backlog und Ticketbaum
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 11](epic-1-issue-11.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Backlog und Ticketbaum als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/Backlog.vue](../../resources/js/pages/projects/Backlog.vue); [resources/js/components/TicketTreeList.vue](../../resources/js/components/TicketTreeList.vue); [app/Actions/Projects/BuildProjectBacklogPayload.php](../../app/Actions/Projects/BuildProjectBacklogPayload.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Backlog mit vorhandenen Filtern und Hierarchie neu darstellen.
|
||||||
|
2. Eltern-Kind-Beziehungen, einklappbare Ebenen und vorhandene Aktionen integrieren.
|
||||||
|
3. Mobile Listenansicht erstellen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Verschachtelte Tickets und leere Treffer funktionieren.
|
||||||
|
- [ ] Berechtigungen und Statusübergänge bleiben erhalten.
|
||||||
|
- [ ] Filter verändern keine fachlichen Daten.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectBacklogTest.php](../../tests/Feature/Projects/ProjectBacklogTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# UI Rework 15: Neue Ticketdetailansicht und Seitenstruktur
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md), [Issue 11](epic-1-issue-11.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Neue Ticketdetailansicht und Seitenstruktur als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue); [app/Actions/Projects/BuildProjectTicketDetailPayload.php](../../app/Actions/Projects/BuildProjectTicketDetailPayload.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
Relevanter Stitch-Screen: Ticket Detail View — 934e3eb46764456195f0eb627e67a363. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Detailansicht in klar abgegrenzte Komponenten aufteilen.
|
||||||
|
2. Desktop-Inhalt und Metadatenbereich sowie mobile Stapelansicht neu aufbauen.
|
||||||
|
3. Stabile Einhängepunkte für Bearbeitung, Checkliste, Beziehungen, Historie und Git definieren.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Alle bestehenden Informationen bleiben zugänglich.
|
||||||
|
- [ ] Keine bestehende Funktion wird beim Umbau entfernt.
|
||||||
|
- [ ] Direkte Ticket-URLs funktionieren.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketControllerTest.php](../../tests/Feature/Projects/ProjectTicketControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# UI Rework 16: Ticketanlage und Bearbeitungsformulare
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 5](epic-1-issue-5.md), [Issue 15](epic-1-issue-15.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Ticketanlage und Bearbeitungsformulare als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/TicketCreate.vue](../../resources/js/pages/projects/TicketCreate.vue); [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue); [app/Http/Requests/StoreProjectTicketRequest.php](../../app/Http/Requests/StoreProjectTicketRequest.php); [app/Http/Requests/UpdateProjectTicketRequest.php](../../app/Http/Requests/UpdateProjectTicketRequest.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
Relevanter Stitch-Screen: Ticket Detail View — 934e3eb46764456195f0eb627e67a363. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Formulare für vorhandene Ticketattribute neu erstellen.
|
||||||
|
2. Gemeinsame Feldkomponenten für Anlage und Bearbeitung verwenden.
|
||||||
|
3. Projektkonfigurierte Felder, Autor-/Story-Point-Schalter und Validierung erhalten.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Anlage und Bearbeitung speichern dieselben Attribute wie bisher.
|
||||||
|
- [ ] Fehler und ungespeicherte Änderungen sind abgedeckt.
|
||||||
|
- [ ] Gesperrte Aktionen bleiben serverseitig abgesichert.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketControllerTest.php](../../tests/Feature/Projects/ProjectTicketControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# UI Rework 17: Akzeptanzkriterien-Checkliste
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 16](epic-1-issue-16.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Akzeptanzkriterien-Checkliste als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/lib/acceptance-criteria.ts](../../resources/js/lib/acceptance-criteria.ts); [resources/js/types/models/acceptance-criteria-model.ts](../../resources/js/types/models/acceptance-criteria-model.ts); [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
Relevanter Stitch-Screen: Ticket Detail View — 934e3eb46764456195f0eb627e67a363. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Bestehende hierarchische Checkliste als neue Komponente aufbauen.
|
||||||
|
2. Hinzufügen, Bearbeiten, Verschachteln und Abhaken entsprechend dem Bestand erhalten.
|
||||||
|
3. In Anlage und Detail integrieren.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Hierarchie und Reihenfolge bleiben beim Speichern erhalten.
|
||||||
|
- [ ] Bestehende Kriterien gehen nicht verloren.
|
||||||
|
- [ ] Touch und Tastatur funktionieren.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketControllerTest.php](../../tests/Feature/Projects/ProjectTicketControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 18: Ticketbeziehungen und Hierarchie
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 15](epic-1-issue-15.md), [Issue 16](epic-1-issue-16.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Ticketbeziehungen und Hierarchie als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue); [app/Http/Controllers/ProjectTicketLinkController.php](../../app/Http/Controllers/ProjectTicketLinkController.php); [resources/js/components/TicketTreeList.vue](../../resources/js/components/TicketTreeList.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Eltern-/Kindanzeige und Ticketverknüpfungen neu gestalten.
|
||||||
|
2. Bestehende Linktypen und Richtung verwenden.
|
||||||
|
3. Erlaubte Hinzufüge-/Entfernaktionen integrieren.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Bestehende Hierarchievalidierung bleibt wirksam.
|
||||||
|
- [ ] Linkrichtung wird korrekt angezeigt.
|
||||||
|
- [ ] Kein neuer Beziehungstyp wird eingeführt.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketLinkControllerTest.php](../../tests/Feature/Projects/ProjectTicketLinkControllerTest.php), [tests/Feature/Projects/ProjectTicketControllerTest.php](../../tests/Feature/Projects/ProjectTicketControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 19: Blocker und Ticketänderungshistorie
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 15](epic-1-issue-15.md), [Issue 16](epic-1-issue-16.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Blocker und Ticketänderungshistorie als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue); [resources/js/types/models/ticket-worklog-entry-model.ts](../../resources/js/types/models/ticket-worklog-entry-model.ts). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Blockieren/Entblockieren und Kommentarerfassung neu darstellen.
|
||||||
|
2. Vorhandenes Worklog als lesbare Historie integrieren.
|
||||||
|
3. Mobile Darstellung und lokale Datumsformatierung nutzen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Blockieren erfordert weiterhin Kommentar.
|
||||||
|
- [ ] Historie zeigt echte Ereignisse und Akteure.
|
||||||
|
- [ ] Keine neue Kommentar- oder Aktivitätsfunktion.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketControllerTest.php](../../tests/Feature/Projects/ProjectTicketControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# UI Rework 2: Emerald-Designsystem und Hell/Dunkel/System
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 1](epic-1-issue-1.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Emerald-Designsystem und Hell/Dunkel/System als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/css/app.css](../../resources/css/app.css); [resources/js/composables/useAppearance.ts](../../resources/js/composables/useAppearance.ts). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Farben, Typografie, Abstände, Radien und Fokuszustände aus Stitch als semantische Tokens definieren.
|
||||||
|
2. Inter lokal ausliefern.
|
||||||
|
3. Hell/Dunkel/System einschließlich initialem Laden umsetzen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Beide Themes sind lesbar.
|
||||||
|
- [ ] Systemwechsel aktualisieren die Ansicht.
|
||||||
|
- [ ] Reduzierte Bewegung wird berücksichtigt.
|
||||||
|
- [ ] Keine Starterkit-Optik.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# UI Rework 20: Git-Bereich und Branch-Erstellung am Ticket
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 5](epic-1-issue-5.md), [Issue 15](epic-1-issue-15.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Git-Bereich und Branch-Erstellung am Ticket als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue); [app/Http/Controllers/ProjectTicketBranchController.php](../../app/Http/Controllers/ProjectTicketBranchController.php); [resources/js/types/models/ticket-repository-branch-model.ts](../../resources/js/types/models/ticket-repository-branch-model.ts). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Branch-/Pull-Request-Anzeigen sowie bestehenden Branch-Erstellablauf in eigene neue Komponenten überführen.
|
||||||
|
2. Repositoryauswahl, Namensvorschlag und vorhandene Prüfungen erhalten.
|
||||||
|
3. Laufende Anfragen, fehlenden Git-Zugriff und Providerfehler als eigene Zustände abbilden. Den Weg zu vorhandenen Verbindungseinstellungen anbieten, Formulareingaben bei Fehlern erhalten und externe Git-Links entsprechend kennzeichnen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Fehlende Git-Verbindung und Providerfehler sind verständlich.
|
||||||
|
- [ ] Branch-Erstellung funktioniert.
|
||||||
|
- [ ] Fremde Git-Links öffnen extern.
|
||||||
|
- [ ] Bestehende Zuordnungsregeln bleiben erhalten.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketBranchControllerTest.php](../../tests/Feature/Projects/ProjectTicketBranchControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 21: Profil und Erscheinungsbild
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Profil und Erscheinungsbild als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/settings/Profile.vue](../../resources/js/pages/settings/Profile.vue); [resources/js/pages/settings/Appearance.vue](../../resources/js/pages/settings/Appearance.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Profilfelder, Avatar-Upload/Entfernen und Erscheinungsbild neu gestalten.
|
||||||
|
2. Gemeinsame Einstellungsnavigation aufbauen.
|
||||||
|
3. Datenschutz durch bestehende Rechte und Endpunkte erhalten.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Avatar und Profiländerungen funktionieren.
|
||||||
|
- [ ] Hell/Dunkel/System bleibt nach Neustart erhalten.
|
||||||
|
- [ ] Uploadfehler und mobile Dateiauswahl sind berücksichtigt.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Settings/ProfileUpdateTest.php](../../tests/Feature/Settings/ProfileUpdateTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 22: Sicherheit und Anwendungsschlüssel
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 6](epic-1-issue-6.md), [Issue 7](epic-1-issue-7.md), [Issue 21](epic-1-issue-21.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Sicherheit und Anwendungsschlüssel als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/settings/Security.vue](../../resources/js/pages/settings/Security.vue); [resources/js/pages/settings/Applications.vue](../../resources/js/pages/settings/Applications.vue); [resources/js/components/TwoFactorSetupModal.vue](../../resources/js/components/TwoFactorSetupModal.vue); [resources/js/components/TwoFactorRecoveryCodes.vue](../../resources/js/components/TwoFactorRecoveryCodes.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Passwortverwaltung, 2FA-Einrichtung, Recovery-Codes und vorhandene Anwendungsschlüssel neu darstellen.
|
||||||
|
2. Sensible Bestätigungsabläufe erhalten.
|
||||||
|
3. Zustände vor und nach 2FA-Bestätigung sowie beim Anzeigen und Erneuern von Recovery-Codes anhand der bestehenden Endpunkte abbilden. Schlüsselanzeige und Kopierfunktion an die vorhandene Freigabelogik binden; fehlgeschlagene Bestätigungen direkt am Formular erklären.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] 2FA-Aktivieren/Deaktivieren und Recovery-Codes funktionieren.
|
||||||
|
- [ ] Schlüssel werden weiterhin nur nach bestehender Logik angezeigt.
|
||||||
|
- [ ] Abbruch und Fehler sind sicher.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Settings/SecurityTest.php](../../tests/Feature/Settings/SecurityTest.php), [tests/Feature/Settings/UserApplicationTest.php](../../tests/Feature/Settings/UserApplicationTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 23: Persönliche Git-Verbindungen
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 21](epic-1-issue-21.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Persönliche Git-Verbindungen als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/settings/GitConnections.vue](../../resources/js/pages/settings/GitConnections.vue); [app/Http/Controllers/Settings/GitConnectionController.php](../../app/Http/Controllers/Settings/GitConnectionController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Vorhandene Git-Verbindungen und Formulare neu gestalten.
|
||||||
|
2. Provider- und Serverangaben klar darstellen.
|
||||||
|
3. Validierungs- und Providerfehler in das neue Rückmeldesystem integrieren.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Erstellen, Bearbeiten und Entfernen bleiben möglich.
|
||||||
|
- [ ] Zugangsdaten werden nicht zusätzlich offengelegt.
|
||||||
|
- [ ] Leere Listen und fehlende Berechtigungen funktionieren.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Settings/GitConnectionTest.php](../../tests/Feature/Settings/GitConnectionTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 24: Allgemeine Projekteinstellungen und Vererbung
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 9](epic-1-issue-9.md), [Issue 5](epic-1-issue-5.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Allgemeine Projekteinstellungen und Vererbung als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/settings/General.vue](../../resources/js/pages/projects/settings/General.vue); [resources/js/components/projects/TemplateInheritancePanel.vue](../../resources/js/components/projects/TemplateInheritancePanel.vue); [app/Http/Controllers/ProjectController.php](../../app/Http/Controllers/ProjectController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Einstellungsnavigation und allgemeine Projektseite neu aufbauen.
|
||||||
|
2. Vererbung und lokale Overrides nachvollziehbar anzeigen.
|
||||||
|
3. Bestehende Vorlagenwechsel-/Reset- und Löschbestätigungen erhalten.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Geerbte und lokale Werte sind unterscheidbar.
|
||||||
|
- [ ] Destruktive Aktionen erfordern bestehende Bestätigung.
|
||||||
|
- [ ] Keine neue Vorlagenlogik.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTemplateOverrideTest.php](../../tests/Feature/Projects/ProjectTemplateOverrideTest.php), [tests/Feature/Projects/ProjectControllerTest.php](../../tests/Feature/Projects/ProjectControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 25: Workflow-Einstellungen für Spalten und Status
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 24](epic-1-issue-24.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Workflow-Einstellungen für Spalten und Status als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/settings/Board.vue](../../resources/js/pages/projects/settings/Board.vue); [app/Http/Controllers/ProjectBoardSettingsController.php](../../app/Http/Controllers/ProjectBoardSettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Spalten, Statuszuordnung, Namen und vorhandene Sortierung neu bearbeiten lassen.
|
||||||
|
2. Backlogstatus und Löschbedingungen aus dem Bestand übernehmen.
|
||||||
|
3. Übergangsmatrix als separate Komponente vorbereiten.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Vorhandene CRUD-Aktionen funktionieren.
|
||||||
|
- [ ] Gültige Positionen bleiben erhalten.
|
||||||
|
- [ ] Genutzte Status und Spalten werden nicht unzulässig entfernt.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectBoardSettingsControllerTest.php](../../tests/Feature/Projects/ProjectBoardSettingsControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 26: Bearbeitung erlaubter Statusübergänge
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 25](epic-1-issue-25.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Bearbeitung erlaubter Statusübergänge als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/settings/Board.vue](../../resources/js/pages/projects/settings/Board.vue); [app/Http/Controllers/ProjectBoardSettingsController.php](../../app/Http/Controllers/ProjectBoardSettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Vorhandene Übergangsregeln als übersichtliche, mobil bedienbare Oberfläche neu erstellen.
|
||||||
|
2. Nur existierende Workflowfähigkeiten abbilden.
|
||||||
|
3. Gespeicherten Zustand und Fehler klar anzeigen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Erlaubte und verbotene Übergänge werden korrekt gespeichert.
|
||||||
|
- [ ] Board-Dropziele entsprechen der Konfiguration.
|
||||||
|
- [ ] Keine neue Workflow-Engine.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectBoardSettingsControllerTest.php](../../tests/Feature/Projects/ProjectBoardSettingsControllerTest.php), [tests/Feature/Projects/ProjectBoardControllerTest.php](../../tests/Feature/Projects/ProjectBoardControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 27: Tickettypen, Prioritäten und Anzeigeoptionen
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 24](epic-1-issue-24.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Tickettypen, Prioritäten und Anzeigeoptionen als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/settings/Metadata.vue](../../resources/js/pages/projects/settings/Metadata.vue); [app/Http/Controllers/ProjectTicketMetadataSettingsController.php](../../app/Http/Controllers/ProjectTicketMetadataSettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Typen, Prioritäten und bestehende Autor-/Story-Point-Optionen neu darstellen.
|
||||||
|
2. Hinzufügen, Umbenennen, Reihenfolge und bestehende Ersetzungsregeln beim Löschen erhalten.
|
||||||
|
3. Geschützte und bearbeitbare Einträge anhand vorhandener Berechtigungen unterscheiden. Vor dem Löschen die benötigte Ersatzzuordnung erklären und Validierungsfehler am betroffenen Eintrag anzeigen, ohne projektseitige Regeln zu verändern.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Geschützte Typen bleiben geschützt.
|
||||||
|
- [ ] Löschersatz und Reihenfolge funktionieren.
|
||||||
|
- [ ] Inhalte bleiben projektkonfigurierbar.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketMetadataSettingsControllerTest.php](../../tests/Feature/Projects/ProjectTicketMetadataSettingsControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 28: Benutzerdefinierte Felder und Linktypen
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 27](epic-1-issue-27.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Benutzerdefinierte Felder und Linktypen als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/settings/Metadata.vue](../../resources/js/pages/projects/settings/Metadata.vue); [app/Http/Controllers/ProjectTicketMetadataSettingsController.php](../../app/Http/Controllers/ProjectTicketMetadataSettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Bestehende Felddefinitionen und Linktypen in separate neue Einstellungsabschnitte aufteilen.
|
||||||
|
2. Vorhandene Feldtypen, Richtung und Gegenbezeichnungen erhalten.
|
||||||
|
3. Anlage, Bearbeitung und vorhandene Löschabläufe über die bestehenden Endpunkte anbinden. Ungültige Definitionen mit konkreten Feldfehlern zurückmelden und bestehende Schutzregeln für bereits verwendete Felder und Linktypen in der Oberfläche nachvollziehbar machen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Keine neuen Feldtypen.
|
||||||
|
- [ ] Vorhandene Feldwerte bleiben erhalten.
|
||||||
|
- [ ] Linktypen und Gegenrichtung werden korrekt angezeigt und bearbeitet.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketMetadataSettingsControllerTest.php](../../tests/Feature/Projects/ProjectTicketMetadataSettingsControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 29: Repository-Einstellungen des Projekts
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 23](epic-1-issue-23.md), [Issue 24](epic-1-issue-24.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Repository-Einstellungen des Projekts als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/settings/Repositories.vue](../../resources/js/pages/projects/settings/Repositories.vue); [app/Http/Controllers/ProjectRepositorySettingsController.php](../../app/Http/Controllers/ProjectRepositorySettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Projekt-Repository-Verbindungen mit vorhandener Discovery, Auswahl und Bearbeitung neu aufbauen.
|
||||||
|
2. Persönliche Verbindungen und Projektzuordnung klar unterscheiden.
|
||||||
|
3. Ladezustände, leere Suchergebnisse, fehlende persönliche Verbindung und Providerfehler getrennt darstellen. Auf die vorhandenen persönlichen Git-Einstellungen verlinken und nach erfolgreicher Zuordnung die Projektliste aus der Serverantwort aktualisieren.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Vorhandene Provider werden unterstützt.
|
||||||
|
- [ ] Fehlende Verbindung und Providerfehler sind abgedeckt.
|
||||||
|
- [ ] Tickets nutzen weiterhin dieselben Repositories.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectRepositorySettingsControllerTest.php](../../tests/Feature/Projects/ProjectRepositorySettingsControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# UI Rework 3: Neue grundlegende UI-Komponenten
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 1](epic-1-issue-1.md), [Issue 2](epic-1-issue-2.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Neue grundlegende UI-Komponenten als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/components/ui](../../resources/js/components/ui). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Buttons, Eingaben, Auswahlfelder, Checkboxen, Badges und Avatare neu gestalten.
|
||||||
|
2. Zugängliches Verhalten vorhandener Headless-Primitiven wiederverwenden.
|
||||||
|
3. Öffentliche Props für Folgetickets stabil festlegen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Tastatur, Fokus, disabled, invalid und loading funktionieren.
|
||||||
|
- [ ] Sichtbare Texte sind übersetzbar.
|
||||||
|
- [ ] Komponenten funktionieren in beiden Themes.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 30: Projekt-Benachrichtigungseinstellungen
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 24](epic-1-issue-24.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Projekt-Benachrichtigungseinstellungen als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/settings/Notifications.vue](../../resources/js/pages/projects/settings/Notifications.vue); [app/Http/Controllers/ProjectNotificationSettingsController.php](../../app/Http/Controllers/ProjectNotificationSettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Bestehende Aktionen, Trigger, Filter und Nachrichtenvorlagen neu gestalten.
|
||||||
|
2. Vorhandene Delivery-Informationen und Validierung nutzen.
|
||||||
|
3. Sensible Webhookfelder sorgfältig darstellen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Bestehende Benachrichtigungen lassen sich unverändert konfigurieren.
|
||||||
|
- [ ] Keine neuen Trigger oder Push-Dienste.
|
||||||
|
- [ ] Fehlerfälle sind getestet.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectNotificationSettingsControllerTest.php](../../tests/Feature/Projects/ProjectNotificationSettingsControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 31: Verwaltung wiederverwendbarer Projektvorlagen
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 9](epic-1-issue-9.md), [Issue 24](epic-1-issue-24.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Verwaltung wiederverwendbarer Projektvorlagen als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/project-templates/Index.vue](../../resources/js/pages/project-templates/Index.vue); [resources/js/pages/project-templates/Create.vue](../../resources/js/pages/project-templates/Create.vue); [resources/js/pages/project-templates/Edit.vue](../../resources/js/pages/project-templates/Edit.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Vorlagenübersicht, Erstellung und Bearbeitung neu aufbauen.
|
||||||
|
2. Auswirkungen auf vererbende Projekte und Bestätigungen darstellen.
|
||||||
|
3. Interne Basisvorlagen weiterhin ausblenden.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Vorhandene Erstell-/Bearbeitungsflüsse funktionieren.
|
||||||
|
- [ ] Unveränderliche Slugs bleiben gesperrt.
|
||||||
|
- [ ] Keine Änderungen an Vererbungsregeln.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTemplateControllerTest.php](../../tests/Feature/Projects/ProjectTemplateControllerTest.php), [tests/Feature/Projects/ProjectTemplateOverrideTest.php](../../tests/Feature/Projects/ProjectTemplateOverrideTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# UI Rework 32: Teams und Mitgliedschaft
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Teams und Mitgliedschaft als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/teams/Index.vue](../../resources/js/pages/teams/Index.vue); [app/Http/Controllers/TeamController.php](../../app/Http/Controllers/TeamController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Teamübersicht, Teamverwaltung und Mitgliedsauswahl im neuen Design umsetzen.
|
||||||
|
2. Alle bestehenden Rechte und Aktionen übernehmen.
|
||||||
|
3. Mobile Bedienung sicherstellen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Team- und Mitgliedsaktionen entsprechen dem Bestand.
|
||||||
|
- [ ] Fehlende Rechte und Validierungsfehler sind abgedeckt.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Teams/TeamControllerTest.php](../../tests/Feature/Teams/TeamControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 33: Projektzugriff und Rechtezuweisung
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 8](epic-1-issue-8.md), [Issue 32](epic-1-issue-32.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Projektzugriff und Rechtezuweisung als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/Access.vue](../../resources/js/pages/projects/Access.vue); [app/Http/Controllers/ProjectController.php](../../app/Http/Controllers/ProjectController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Direkte Benutzerrechte und Teamrechte neu darstellen.
|
||||||
|
2. Effektive Rechte und Bearbeitungsgrenzen nachvollziehbar machen.
|
||||||
|
3. Bestehende Zuweisungsendpunkte weiterverwenden.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Keine neue Rollenlogik.
|
||||||
|
- [ ] Rechte gelten weiterhin additiv nach Bestand.
|
||||||
|
- [ ] Unberechtigte Zuweisungen werden serverseitig abgewiesen.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectControllerTest.php](../../tests/Feature/Projects/ProjectControllerTest.php), [tests/Feature/Permissions/PermissionServiceTest.php](../../tests/Feature/Permissions/PermissionServiceTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 34: Benutzeradministration
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Benutzeradministration als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/admin/users/Index.vue](../../resources/js/pages/admin/users/Index.vue); [resources/js/pages/admin/users/Create.vue](../../resources/js/pages/admin/users/Create.vue); [resources/js/pages/admin/users/Edit.vue](../../resources/js/pages/admin/users/Edit.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Übersicht, Benutzeranlage und Bearbeitung vollständig neu gestalten.
|
||||||
|
2. Vorhandene Felder, Adminoptionen und erzwungenen Passwortwechsel erhalten.
|
||||||
|
3. Verfügbare Aktionen aus den bestehenden Berechtigungen ableiten, sensible Änderungen bestätigen lassen und Validierungsfehler direkt den Eingaben zuordnen. Nach Speichern oder Abbruch zur passenden Übersicht zurückführen, ohne neue Rollen oder Verwaltungsaktionen einzuführen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Bestehende Adminaktionen funktionieren.
|
||||||
|
- [ ] Nicht-Admins erhalten keinen Zugriff.
|
||||||
|
- [ ] Formfehler und sensible Aktionen sind abgedeckt.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Admin/UserManagementControllerTest.php](../../tests/Feature/Admin/UserManagementControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# UI Rework 35: Öffentliche Einstiegsseite
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 2](epic-1-issue-2.md), [Issue 3](epic-1-issue-3.md), [Issue 6](epic-1-issue-6.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Öffentliche Einstiegsseite als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/Welcome.vue](../../resources/js/pages/Welcome.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
Relevanter Stitch-Screen: Landing Page - Open Source & Self-Hosted — 41a744db2b1f4e01a9c597835b58759e. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Neue kompakte öffentliche Einstiegsseite mit KanbanGit-Branding und Anmeldung entwickeln.
|
||||||
|
2. Stitch-Landingpage ausschließlich als Stilreferenz nutzen.
|
||||||
|
3. Nur vorhandene Funktionen beschreiben.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Keine erfundenen Produktfunktionen, Tarife oder Statistiken.
|
||||||
|
- [ ] Responsiv und lokalisierbar.
|
||||||
|
- [ ] Einstieg in Anmeldung funktioniert.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 36: Tauri-Grundgerüst und macOS-Entwicklungsbuild
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: Keine.
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Tauri-Grundgerüst und macOS-Entwicklungsbuild als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: `native/` (in diesem Ticket neu anzulegen); [.gitignore](../../.gitignore). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
Technische Referenz: [Tauri 2](https://v2.tauri.app/), [Remote-Inhalte und native Berechtigungen](https://v2.tauri.app/security/capabilities/).
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Isoliertes Tauri-2-Projekt im gemeinsamen Repository anlegen.
|
||||||
|
2. Minimale lokale Startansicht und Server-WebView erstellen.
|
||||||
|
3. Entwicklungsbuild auf macOS mit vorhandenem KanbanGit prüfen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Laravel bleibt am bisherigen Ort.
|
||||||
|
- [ ] macOS-App startet und zeigt die Serveroberfläche.
|
||||||
|
- [ ] Keine allgemeine native API für Remote-Inhalte.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# UI Rework 37: Serverauswahl, Sitzungen und Verbindungsfehler
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 36](epic-1-issue-36.md), [Issue 6](epic-1-issue-6.md), [Issue 7](epic-1-issue-7.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Serverauswahl, Sitzungen und Verbindungsfehler als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: `native` (entsteht ab Issue 36); [resources/js/pages/auth](../../resources/js/pages/auth). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
Technische Referenz: [Tauri 2](https://v2.tauri.app/), [Remote-Inhalte und native Berechtigungen](https://v2.tauri.app/security/capabilities/).
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Eine aktive, frei wählbare HTTPS-Installation speichern.
|
||||||
|
2. Serverwechsel und lokale Fehleransicht umsetzen.
|
||||||
|
3. Sitzungen, Logout, 2FA, Start ohne Verbindung und Formularfehler prüfen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Kein Zertifikats-Bypass.
|
||||||
|
- [ ] Sitzungserhalt folgt Laravel.
|
||||||
|
- [ ] Serverwechsel übernimmt keine fremden Zugangsdaten.
|
||||||
|
- [ ] Bestehende Eingaben werden bei Verbindungsfehler nicht durch einen leeren Bildschirm ersetzt.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 38: Android-App und Plattformanpassung
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 37](epic-1-issue-37.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Android-App und Plattformanpassung als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: `native (Android-Plattformprojekt)` (entsteht ab Issue 36). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
Technische Referenz: [Tauri 2](https://v2.tauri.app/).
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Android-Ziel konfigurieren und Entwicklungsbuild erstellen.
|
||||||
|
2. Zurück-Taste, Systemleisten, Safe Areas und Bildschirmtastatur anpassen.
|
||||||
|
3. Bestehende Server-/Anmeldeabläufe prüfen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] App startet auf Emulator und geeignetem Gerät.
|
||||||
|
- [ ] Login, 2FA, Navigation und Wiederaufnahme funktionieren.
|
||||||
|
- [ ] Keine eigenständige zweite Fachoberfläche.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# UI Rework 39: iOS/iPadOS-App für lokale Xcode-Builds
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 37](epic-1-issue-37.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
iOS/iPadOS-App für lokale Xcode-Builds als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: `native (Apple-Plattformprojekt)` (entsteht ab Issue 36). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
Technische Referenz: [Tauri 2](https://v2.tauri.app/).
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. IOS/iPadOS-Ziel für lokalen Xcode-Build konfigurieren.
|
||||||
|
2. WKWebView-Sitzungen, Safe Areas, Tastatur und Größenwechsel prüfen.
|
||||||
|
3. Benötigte lokale Einrichtung im Ticket dokumentieren.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Simulatorbuild funktioniert.
|
||||||
|
- [ ] Gerätebuild nutzt vom Nutzer bereitgestellte Signierung.
|
||||||
|
- [ ] Kein TestFlight- oder Store-Upload.
|
||||||
|
- [ ] IPad bleibt bedienbar.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# UI Rework 4: Responsive Anwendungsnavigation
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 3](epic-1-issue-3.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Responsive Anwendungsnavigation als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/layouts](../../resources/js/layouts); [resources/js/components/AppSidebar.vue](../../resources/js/components/AppSidebar.vue); [resources/js/types/navigation.ts](../../resources/js/types/navigation.ts). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Neue Sidebar, obere Werkzeugleiste, Projektnavigation und mobile Navigation aufbauen.
|
||||||
|
2. Nur vorhandene und erlaubte Bereiche verlinken.
|
||||||
|
3. Aktuelle Auswahl und kompakte Bildschirmgrößen berücksichtigen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Alle bestehenden Bereiche bleiben erreichbar.
|
||||||
|
- [ ] Rollen sehen nur erlaubte Links.
|
||||||
|
- [ ] Browser-Zurück und mobile Navigation funktionieren.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 40: App-Links, externe Links und Dateiauswahl
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 20](epic-1-issue-20.md), [Issue 21](epic-1-issue-21.md), [Issue 38](epic-1-issue-38.md), [Issue 39](epic-1-issue-39.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
App-Links, externe Links und Dateiauswahl als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: `native` (entsteht ab Issue 36); [resources/js/components](../../resources/js/components). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
Technische Referenz: [Tauri 2](https://v2.tauri.app/), [Remote-Inhalte und native Berechtigungen](https://v2.tauri.app/security/capabilities/).
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Interne App-Links zur ausgewählten Installation und externe Browserlinks umsetzen.
|
||||||
|
2. Avatar-Dateiauswahl, Zwischenablage und Browser-Zurück plattformübergreifend absichern.
|
||||||
|
3. Native Schnittstellen eng begrenzen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Fremde Links wechseln nicht ungefragt den Server.
|
||||||
|
- [ ] Externe Git-Links funktionieren.
|
||||||
|
- [ ] Uploads und Kopieren funktionieren auf allen App-Plattformen.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 41: Signierter macOS-Release-Download
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 40](epic-1-issue-40.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Signierter macOS-Release-Download als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: `native` (entsteht ab Issue 36); [.github/workflows](../../.github/workflows). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
Technische Referenz: [Tauri 2](https://v2.tauri.app/), [macOS-Signierung](https://v2.tauri.app/distribute/sign/macos/).
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Reproduzierbaren macOS-Releasebuild mit Signierung und Notarisierung vorbereiten.
|
||||||
|
2. Zertifikate nur als externe Secrets erwarten.
|
||||||
|
3. Installationsartefakt für Git-Releases erzeugen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Builds verwenden keine eingecheckten Schlüssel.
|
||||||
|
- [ ] Signiertes Artefakt lässt sich mit bereitgestellten Voraussetzungen installieren.
|
||||||
|
- [ ] Fehlende Secrets werden klar gemeldet.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# UI Rework 42: Signiertes Android-APK für Git-Releases
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 40](epic-1-issue-40.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Signiertes Android-APK für Git-Releases als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: `native` (entsteht ab Issue 36); [.github/workflows](../../.github/workflows). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
Technische Referenz: [Tauri 2](https://v2.tauri.app/), [Android-Releasevorbereitung](https://developer.android.com/studio/publish/preparing).
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Reproduzierbaren signierten APK-Releasebuild konfigurieren.
|
||||||
|
2. Dauerhafte Signing-Identität über Secrets beziehen.
|
||||||
|
3. Versionierung und Upgrade vorhandener Installationen berücksichtigen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] APK ist direkt installierbar.
|
||||||
|
- [ ] Updates behalten Daten/Sitzungen entsprechend Plattformverhalten.
|
||||||
|
- [ ] Schlüssel fehlen im Repository.
|
||||||
|
- [ ] Kein Play-Store-Upload.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# UI Rework 43: Getrennte Prüf- und Build-Pipelines
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 39](epic-1-issue-39.md), [Issue 41](epic-1-issue-41.md), [Issue 42](epic-1-issue-42.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Getrennte Prüf- und Build-Pipelines als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [Dockerfile](../../Dockerfile); [.github/workflows/ci_cd.yml](../../.github/workflows/ci_cd.yml); [.github/tests/test_and_check_app/action.yml](../../.github/tests/test_and_check_app/action.yml). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
Technische Referenz: [Tauri 2](https://v2.tauri.app/).
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Laravel/Web-Prüfungen von Tauri-Builds trennen.
|
||||||
|
2. Passende Linux-/macOS-Runner festlegen.
|
||||||
|
3. Änderungen nach Pfaden zuordnen, gemeinsame Änderungen vollständig prüfen.
|
||||||
|
4. Bestehendes Serverrelease erhalten.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Serverimage enthält keine native Toolchain.
|
||||||
|
- [ ] Erforderliche Prüfungen werden nicht übersprungen.
|
||||||
|
- [ ] macOS/Android-Artefakte sind getrennt.
|
||||||
|
- [ ] iOS bleibt Selbstbau.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 44: Web-Abnahme und Entfernung des alten Frontends
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 1](epic-1-issue-1.md), [Issue 2](epic-1-issue-2.md), [Issue 3](epic-1-issue-3.md), [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md), [Issue 6](epic-1-issue-6.md), [Issue 7](epic-1-issue-7.md), [Issue 8](epic-1-issue-8.md), [Issue 9](epic-1-issue-9.md), [Issue 10](epic-1-issue-10.md), [Issue 11](epic-1-issue-11.md), [Issue 12](epic-1-issue-12.md), [Issue 13](epic-1-issue-13.md), [Issue 14](epic-1-issue-14.md), [Issue 15](epic-1-issue-15.md), [Issue 16](epic-1-issue-16.md), [Issue 17](epic-1-issue-17.md), [Issue 18](epic-1-issue-18.md), [Issue 19](epic-1-issue-19.md), [Issue 20](epic-1-issue-20.md), [Issue 21](epic-1-issue-21.md), [Issue 22](epic-1-issue-22.md), [Issue 23](epic-1-issue-23.md), [Issue 24](epic-1-issue-24.md), [Issue 25](epic-1-issue-25.md), [Issue 26](epic-1-issue-26.md), [Issue 27](epic-1-issue-27.md), [Issue 28](epic-1-issue-28.md), [Issue 29](epic-1-issue-29.md), [Issue 30](epic-1-issue-30.md), [Issue 31](epic-1-issue-31.md), [Issue 32](epic-1-issue-32.md), [Issue 33](epic-1-issue-33.md), [Issue 34](epic-1-issue-34.md), [Issue 35](epic-1-issue-35.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Web-Abnahme und Entfernung des alten Frontends als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js](../../resources/js); [resources/css](../../resources/css); [tests/Feature](../../tests/Feature). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Alle vorhandenen Seiten und Aktionen gegen die Bestandsliste prüfen.
|
||||||
|
2. Desktop/Tablet/Mobil sowie Hell/Dunkel/System visuell und funktional abnehmen.
|
||||||
|
3. Nur nicht mehr referenzierte alte UI-Dateien entfernen.
|
||||||
|
4. Größere neue Befunde in eigene Folgeaufgaben abgrenzen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Keine Starterkit-Seite oder unübersetzte neue UI-Texte.
|
||||||
|
- [ ] Keine verlorenen Bestandsfunktionen.
|
||||||
|
- [ ] Keine bestehenden Tests entfernt.
|
||||||
|
- [ ] Relevante Tests, Build, Lint und abschließender Typecheck bestehen.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# UI Rework 45: Plattformübergreifende App-Abnahme
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 40](epic-1-issue-40.md), [Issue 41](epic-1-issue-41.md), [Issue 42](epic-1-issue-42.md), [Issue 43](epic-1-issue-43.md), [Issue 44](epic-1-issue-44.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Plattformübergreifende App-Abnahme als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: `native` (entsteht ab Issue 36); [resources/js](../../resources/js). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. macOS, Android und iOS/iPadOS mit fertigem Webfrontend prüfen.
|
||||||
|
2. Serverwechsel, Sessionablauf, Serverupdate, Verbindungsabbruch, Links und Dateiauswahl abnehmen.
|
||||||
|
3. Dokumentierte reproduzierbare Befunde mit klaren Reproduktionsschritten festhalten.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Alle vereinbarten App-Abläufe bestehen.
|
||||||
|
- [ ] Keine Produktionsdaten als Testfixtures verändert.
|
||||||
|
- [ ] Releasewege entsprechen Vereinbarung.
|
||||||
|
- [ ] Größere Defekte werden nicht im Abnahmeticket versteckt.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# UI Rework 5: Dialoge, Rückmeldungen und Eingabeschutz
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 3](epic-1-issue-3.md), [Issue 4](epic-1-issue-4.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Dialoge, Rückmeldungen und Eingabeschutz als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/components](../../resources/js/components); [resources/js/composables](../../resources/js/composables). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Dialoge, Menüs, Leer-, Lade- und Fehlerzustände vereinheitlichen.
|
||||||
|
2. Formfehler und Wiederholaktionen definieren.
|
||||||
|
3. Ungespeicherte Eingaben gegen Navigation und fehlgeschlagene Requests absichern.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Keine verlorenen Eingaben bei Requestfehlern.
|
||||||
|
- [ ] Dialogfokus wird korrekt zurückgegeben.
|
||||||
|
- [ ] Netzwerkfehler und serverseitige Validierung sind unterscheidbar.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 6: Anmeldung, E-Mail-Verifikation und 2FA-Challenge
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Anmeldung, E-Mail-Verifikation und 2FA-Challenge als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/auth/Login.vue](../../resources/js/pages/auth/Login.vue); [resources/js/pages/auth/VerifyEmail.vue](../../resources/js/pages/auth/VerifyEmail.vue); [resources/js/pages/auth/TwoFactorChallenge.vue](../../resources/js/pages/auth/TwoFactorChallenge.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Die drei Ansichten vollständig neu erstellen.
|
||||||
|
2. Bestehende Fortify-Routen und Sessionflüsse verwenden.
|
||||||
|
3. Mobile Tastatur, Autofill und Recovery-Codes berücksichtigen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Normale und zweistufige Anmeldung funktionieren.
|
||||||
|
- [ ] Fehler erhalten passende Eingaben.
|
||||||
|
- [ ] Bestehende Autorisierungsregeln bleiben wirksam.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Auth/AuthenticationTest.php](../../tests/Feature/Auth/AuthenticationTest.php), [tests/Feature/Auth/TwoFactorChallengeTest.php](../../tests/Feature/Auth/TwoFactorChallengeTest.php), [tests/Feature/Auth/EmailVerificationTest.php](../../tests/Feature/Auth/EmailVerificationTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 7: Passwort- und Bestätigungsseiten
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 6](epic-1-issue-6.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Passwort- und Bestätigungsseiten als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/auth/ForgotPassword.vue](../../resources/js/pages/auth/ForgotPassword.vue); [resources/js/pages/auth/ResetPassword.vue](../../resources/js/pages/auth/ResetPassword.vue); [resources/js/pages/auth/ConfirmPassword.vue](../../resources/js/pages/auth/ConfirmPassword.vue); [resources/js/pages/auth/ForcePasswordChange.vue](../../resources/js/pages/auth/ForcePasswordChange.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Passwort-Reset, sensible Aktionsbestätigung und erzwungenen Passwortwechsel neu gestalten.
|
||||||
|
2. Bestehende Fortify-Verträge erhalten.
|
||||||
|
3. Zustände abgelaufener oder ungültiger Links berücksichtigen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Alle vier Flüsse funktionieren mit bestehenden Endpunkten.
|
||||||
|
- [ ] Sensible Werte werden nicht in Entwürfen gespeichert.
|
||||||
|
- [ ] Erfolg und Fehler sind klar.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Auth/PasswordResetTest.php](../../tests/Feature/Auth/PasswordResetTest.php), [tests/Feature/Auth/PasswordConfirmationTest.php](../../tests/Feature/Auth/PasswordConfirmationTest.php), [tests/Feature/Settings/SecurityTest.php](../../tests/Feature/Settings/SecurityTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 8: Projektübersicht und Projekteinstieg
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Projektübersicht und Projekteinstieg als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/Index.vue](../../resources/js/pages/projects/Index.vue); [resources/js/pages/projects/Show.vue](../../resources/js/pages/projects/Show.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Projektübersicht und Projekteinstieg neu aufbauen.
|
||||||
|
2. Erlaubte Aktionen und direkte Wege zu Board, Backlog und Einstellungen anbieten.
|
||||||
|
3. Vorhandene Resources nutzen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Nur sichtbare Projekte erscheinen.
|
||||||
|
- [ ] Leerzustand und eingeschränkte Rechte sind abgedeckt.
|
||||||
|
- [ ] Keine Mockup-Funktionen ohne Backend.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectControllerTest.php](../../tests/Feature/Projects/ProjectControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# UI Rework 9: Projektanlage, Duplikation und Vorlagenauswahl
|
||||||
|
|
||||||
|
Epic: [Epic 1](epic-1.md)
|
||||||
|
|
||||||
|
Abhängigkeiten: [Issue 8](epic-1-issue-8.md)
|
||||||
|
|
||||||
|
## Ziel und Grenzen
|
||||||
|
|
||||||
|
Projektanlage, Duplikation und Vorlagenauswahl als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
||||||
|
|
||||||
|
## Einstieg
|
||||||
|
|
||||||
|
Gezielt prüfen: [resources/js/pages/projects/Create.vue](../../resources/js/pages/projects/Create.vue); [app/Http/Controllers/ProjectController.php](../../app/Http/Controllers/ProjectController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
||||||
|
|
||||||
|
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
||||||
|
|
||||||
|
## Implementierungsweg
|
||||||
|
|
||||||
|
1. Neuen Erstellablauf für leeres Projekt, Duplikat und Vorlage bauen.
|
||||||
|
2. Bedingte Felder und Kopieroptionen aus dem Bestand übernehmen.
|
||||||
|
3. Backend-Verträge nur bei Darstellungsbedarf anpassen.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
|
||||||
|
- [ ] Alle drei vorhandenen Erstellwege bleiben korrekt.
|
||||||
|
- [ ] Validierung ist verständlich.
|
||||||
|
- [ ] Operative Ticketdaten werden weiterhin nicht dupliziert.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectControllerTest.php](../../tests/Feature/Projects/ProjectControllerTest.php), [tests/Feature/Projects/ProjectTemplateControllerTest.php](../../tests/Feature/Projects/ProjectTemplateControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
||||||
|
|
||||||
|
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
||||||
|
|
||||||
|
## Grenze der Agentensitzung
|
||||||
|
|
||||||
|
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Epic 1: KanbanGit UI Rework und Tauri-Apps
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Die vorhandene, aus dem Laravel-Starterkit entstandene Oberfläche vollständig durch eine neue Vue/Inertia-Oberfläche im Stil von [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015) ersetzen. Alle bestehenden Produktfunktionen erhalten ein neues, auf Desktop und Mobilgeräte abgestimmtes Erscheinungsbild.
|
||||||
|
|
||||||
|
Eine gemeinsame servergeladene Oberfläche versorgt Browser sowie Tauri-Apps für macOS, iOS/iPadOS und Android. Backend und Clients bleiben im selben Repository. Selbst gehostete Installationen werden über eine frei wählbare HTTPS-Adresse angebunden; jeweils eine Installation ist aktiv.
|
||||||
|
|
||||||
|
Zum Zielumfang gehören Hell/Dunkel/System, eine Lokalisierungsstruktur mit zunächst englischen Texten und bessere Frontend-Bedienung einschließlich Drag-and-drop für bestehende Statuswechsel. Anpassungen an Resources, Inertia-Daten und erforderlichen Routen sind zulässig.
|
||||||
|
|
||||||
|
Ausgeschlossen sind neue fachliche Backend-Funktionen, zusätzliche Mockup-Module, Offline-Datensynchronisierung, persistierte freie Kartenreihenfolge und Hintergrund-Push. macOS und Android werden als signierte Git-Release-Downloads vorbereitet; iOS/iPadOS wird zunächst selbst über Xcode gebaut. Stores folgen später.
|
||||||
|
|
||||||
|
## Abschluss
|
||||||
|
|
||||||
|
Die neue Oberfläche deckt die bestehenden Funktionen ab, funktioniert auf allen vereinbarten Plattformen und hat das alte Starterkit-Frontend vollständig abgelöst. Die Umsetzung ist in eigenständige, begrenzte Agentensitzungen aufgeteilt.
|
||||||
Generated
+30
-29
@@ -418,29 +418,43 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||||
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
|
"integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@humanfs/types": "^0.15.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.0"
|
"node": ">=18.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanfs/node": {
|
"node_modules/@humanfs/node": {
|
||||||
"version": "0.16.7",
|
"version": "0.16.8",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
|
||||||
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
|
"integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@humanfs/core": "^0.19.1",
|
"@humanfs/core": "^0.19.2",
|
||||||
|
"@humanfs/types": "^0.15.0",
|
||||||
"@humanwhocodes/retry": "^0.4.0"
|
"@humanwhocodes/retry": "^0.4.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.0"
|
"node": ">=18.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@humanfs/types": {
|
||||||
|
"version": "0.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
|
||||||
|
"integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/module-importer": {
|
"node_modules/@humanwhocodes/module-importer": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||||
@@ -1303,7 +1317,6 @@
|
|||||||
"integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
|
"integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
@@ -1320,7 +1333,6 @@
|
|||||||
"integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
|
"integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.12.2",
|
"@eslint-community/regexpp": "^4.12.2",
|
||||||
"@typescript-eslint/scope-manager": "8.58.1",
|
"@typescript-eslint/scope-manager": "8.58.1",
|
||||||
@@ -1360,7 +1372,6 @@
|
|||||||
"integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
|
"integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.58.1",
|
"@typescript-eslint/scope-manager": "8.58.1",
|
||||||
"@typescript-eslint/types": "8.58.1",
|
"@typescript-eslint/types": "8.58.1",
|
||||||
@@ -2111,7 +2122,6 @@
|
|||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -3020,7 +3030,6 @@
|
|||||||
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3081,7 +3090,6 @@
|
|||||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"eslint-config-prettier": "bin/cli.js"
|
"eslint-config-prettier": "bin/cli.js"
|
||||||
},
|
},
|
||||||
@@ -3208,7 +3216,6 @@
|
|||||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.9",
|
"array-includes": "^3.1.9",
|
||||||
@@ -3351,7 +3358,6 @@
|
|||||||
"integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==",
|
"integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"globals": "^13.24.0",
|
"globals": "^13.24.0",
|
||||||
@@ -4597,9 +4603,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
|
||||||
"integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
|
"integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -5454,9 +5460,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-selector-parser": {
|
"node_modules/postcss-selector-parser": {
|
||||||
"version": "6.1.2",
|
"version": "6.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz",
|
||||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
"integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5483,7 +5489,6 @@
|
|||||||
"integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==",
|
"integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -5614,9 +5619,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.15.3",
|
"version": "6.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
|
||||||
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
|
"integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-define-property": "^1.0.1",
|
"es-define-property": "^1.0.1",
|
||||||
@@ -6525,7 +6530,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -6591,7 +6595,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"napi-postinstall": "^0.3.0"
|
"napi-postinstall": "^0.3.0"
|
||||||
},
|
},
|
||||||
@@ -6642,7 +6645,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
|
||||||
"integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
|
"integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lightningcss": "^1.33.0",
|
"lightningcss": "^1.33.0",
|
||||||
"picomatch": "^4.0.5",
|
"picomatch": "^4.0.5",
|
||||||
@@ -6998,7 +7000,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
|
||||||
"integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
|
"integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.30",
|
"@vue/compiler-dom": "3.5.30",
|
||||||
"@vue/compiler-sfc": "3.5.30",
|
"@vue/compiler-sfc": "3.5.30",
|
||||||
|
|||||||
@@ -121,9 +121,7 @@ const parseChecklistLine = (
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const indentLevel = clampIndent(
|
const indentLevel = clampIndent(Math.floor(match.groups.indent.length / 2));
|
||||||
Math.floor(match.groups.indent.length / 2),
|
|
||||||
);
|
|
||||||
let content = match.groups.content.trim();
|
let content = match.groups.content.trim();
|
||||||
let checked = false;
|
let checked = false;
|
||||||
|
|
||||||
|
|||||||
@@ -52,12 +52,16 @@ const breadcrumbs: BreadcrumbItem[] = [
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<Form
|
<Form
|
||||||
v-bind="UserManagementController.store.form()"
|
v-bind="UserManagementController.store.form()"
|
||||||
:transform="(data) => ({
|
:transform="
|
||||||
...data,
|
(data) => ({
|
||||||
must_change_password_on_next_login:
|
...data,
|
||||||
data.must_change_password_on_next_login === true ||
|
must_change_password_on_next_login:
|
||||||
data.must_change_password_on_next_login === 'on',
|
data.must_change_password_on_next_login ===
|
||||||
})"
|
true ||
|
||||||
|
data.must_change_password_on_next_login ===
|
||||||
|
'on',
|
||||||
|
})
|
||||||
|
"
|
||||||
class="space-y-6"
|
class="space-y-6"
|
||||||
v-slot="{ errors, processing }"
|
v-slot="{ errors, processing }"
|
||||||
>
|
>
|
||||||
@@ -128,13 +132,19 @@ const breadcrumbs: BreadcrumbItem[] = [
|
|||||||
<span class="block font-medium">
|
<span class="block font-medium">
|
||||||
Must change password on next login
|
Must change password on next login
|
||||||
</span>
|
</span>
|
||||||
<span class="text-muted-foreground block text-sm">
|
<span
|
||||||
Redirect the user to the security settings page until they save a new password.
|
class="text-muted-foreground block text-sm"
|
||||||
|
>
|
||||||
|
Redirect the user to the security
|
||||||
|
settings page until they save a new
|
||||||
|
password.
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
<InputError
|
<InputError
|
||||||
:message="errors.must_change_password_on_next_login"
|
:message="
|
||||||
|
errors.must_change_password_on_next_login
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ import {
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import AppLayout from '@/layouts/AppLayout.vue';
|
import AppLayout from '@/layouts/AppLayout.vue';
|
||||||
import { index as projectsIndex, show as projectShow } from '@/routes/projects/index';
|
import {
|
||||||
|
index as projectsIndex,
|
||||||
|
show as projectShow,
|
||||||
|
} from '@/routes/projects/index';
|
||||||
import {
|
import {
|
||||||
create as projectTemplateCreate,
|
create as projectTemplateCreate,
|
||||||
store as projectTemplateStore,
|
store as projectTemplateStore,
|
||||||
@@ -40,7 +43,9 @@ const breadcrumbs: BreadcrumbItem[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
copy_areas: props.copyAreaOptions.map((copyAreaOption) => copyAreaOption.value),
|
copy_areas: props.copyAreaOptions.map(
|
||||||
|
(copyAreaOption) => copyAreaOption.value,
|
||||||
|
),
|
||||||
name: `${props.project.name} template`,
|
name: `${props.project.name} template`,
|
||||||
slug: `${props.project.key.toLowerCase()}-template`,
|
slug: `${props.project.key.toLowerCase()}-template`,
|
||||||
});
|
});
|
||||||
@@ -48,7 +53,9 @@ const form = useForm({
|
|||||||
const copyAreasError = computed(() => {
|
const copyAreasError = computed(() => {
|
||||||
return (
|
return (
|
||||||
form.errors.copy_areas ??
|
form.errors.copy_areas ??
|
||||||
Object.entries(form.errors).find(([key]) => key.startsWith('copy_areas.'))?.[1] ??
|
Object.entries(form.errors).find(([key]) =>
|
||||||
|
key.startsWith('copy_areas.'),
|
||||||
|
)?.[1] ??
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -66,7 +73,9 @@ const submit = (): void => {
|
|||||||
|
|
||||||
<div class="mx-auto flex w-full max-w-3xl flex-1 flex-col gap-6 p-4">
|
<div class="mx-auto flex w-full max-w-3xl flex-1 flex-col gap-6 p-4">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<Badge variant="outline" class="px-3 py-1">{{ project.key }}</Badge>
|
<Badge variant="outline" class="px-3 py-1">{{
|
||||||
|
project.key
|
||||||
|
}}</Badge>
|
||||||
<Heading
|
<Heading
|
||||||
:title="`Save ${project.name} as new template`"
|
:title="`Save ${project.name} as new template`"
|
||||||
description="Capture reusable project configuration without copying operational ticket data."
|
description="Capture reusable project configuration without copying operational ticket data."
|
||||||
@@ -77,7 +86,8 @@ const submit = (): void => {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Template details</CardTitle>
|
<CardTitle>Template details</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Pick the configuration groups that should be reusable in future projects.
|
Pick the configuration groups that should be reusable in
|
||||||
|
future projects.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
@@ -85,15 +95,26 @@ const submit = (): void => {
|
|||||||
<form class="space-y-6" @submit.prevent="submit">
|
<form class="space-y-6" @submit.prevent="submit">
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<Label for="name">Template name</Label>
|
<Label for="name">Template name</Label>
|
||||||
<Input id="name" v-model="form.name" required placeholder="Foundation template" />
|
<Input
|
||||||
|
id="name"
|
||||||
|
v-model="form.name"
|
||||||
|
required
|
||||||
|
placeholder="Foundation template"
|
||||||
|
/>
|
||||||
<InputError :message="form.errors.name" />
|
<InputError :message="form.errors.name" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<Label for="slug">Template slug</Label>
|
<Label for="slug">Template slug</Label>
|
||||||
<Input id="slug" v-model="form.slug" required placeholder="foundation-template" />
|
<Input
|
||||||
|
id="slug"
|
||||||
|
v-model="form.slug"
|
||||||
|
required
|
||||||
|
placeholder="foundation-template"
|
||||||
|
/>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
Slugs are normalized automatically and used to identify reusable templates.
|
Slugs are normalized automatically and used to
|
||||||
|
identify reusable templates.
|
||||||
</p>
|
</p>
|
||||||
<InputError :message="form.errors.slug" />
|
<InputError :message="form.errors.slug" />
|
||||||
</div>
|
</div>
|
||||||
@@ -102,7 +123,8 @@ const submit = (): void => {
|
|||||||
<div>
|
<div>
|
||||||
<Label>Configuration groups</Label>
|
<Label>Configuration groups</Label>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
Only the selected groups will be stored in the template.
|
Only the selected groups will be stored in
|
||||||
|
the template.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -119,8 +141,12 @@ const submit = (): void => {
|
|||||||
class="mt-1"
|
class="mt-1"
|
||||||
/>
|
/>
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="font-medium">{{ copyAreaOption.label }}</div>
|
<div class="font-medium">
|
||||||
<p class="text-muted-foreground text-sm">
|
{{ copyAreaOption.label }}
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="text-muted-foreground text-sm"
|
||||||
|
>
|
||||||
{{ copyAreaOption.description }}
|
{{ copyAreaOption.description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -135,7 +161,9 @@ const submit = (): void => {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button as-child variant="outline">
|
<Button as-child variant="outline">
|
||||||
<Link :href="projectShow(project.id)">Cancel</Link>
|
<Link :href="projectShow(project.id)"
|
||||||
|
>Cancel</Link
|
||||||
|
>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -44,12 +44,16 @@ const form = useForm({
|
|||||||
const copyAreasError = computed(() => {
|
const copyAreasError = computed(() => {
|
||||||
return (
|
return (
|
||||||
form.errors.copy_areas ??
|
form.errors.copy_areas ??
|
||||||
Object.entries(form.errors).find(([key]) => key.startsWith('copy_areas.'))?.[1] ??
|
Object.entries(form.errors).find(([key]) =>
|
||||||
|
key.startsWith('copy_areas.'),
|
||||||
|
)?.[1] ??
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const linkedProjectCount = computed(() => props.template.linked_project_count ?? 0);
|
const linkedProjectCount = computed(
|
||||||
|
() => props.template.linked_project_count ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
const submit = (): void => {
|
const submit = (): void => {
|
||||||
form.patch(projectTemplatesUpdate.url(props.template.id), {
|
form.patch(projectTemplatesUpdate.url(props.template.id), {
|
||||||
@@ -65,9 +69,12 @@ const submit = (): void => {
|
|||||||
<div class="mx-auto flex w-full max-w-3xl flex-1 flex-col gap-6 p-4">
|
<div class="mx-auto flex w-full max-w-3xl flex-1 flex-col gap-6 p-4">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex flex-wrap items-center gap-3">
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
<Badge variant="outline" class="px-3 py-1">{{ template.slug }}</Badge>
|
<Badge variant="outline" class="px-3 py-1">{{
|
||||||
|
template.slug
|
||||||
|
}}</Badge>
|
||||||
<Badge v-if="linkedProjectCount > 0" variant="secondary">
|
<Badge v-if="linkedProjectCount > 0" variant="secondary">
|
||||||
{{ linkedProjectCount }} linked {{ linkedProjectCount === 1 ? 'project' : 'projects' }}
|
{{ linkedProjectCount }} linked
|
||||||
|
{{ linkedProjectCount === 1 ? 'project' : 'projects' }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<Heading
|
<Heading
|
||||||
@@ -81,10 +88,14 @@ const submit = (): void => {
|
|||||||
<CardTitle>Template details</CardTitle>
|
<CardTitle>Template details</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<template v-if="template.source_project">
|
<template v-if="template.source_project">
|
||||||
This template baseline is rebuilt from {{ template.source_project.name }} ({{ template.source_project.key }}) when you save.
|
This template baseline is rebuilt from
|
||||||
|
{{ template.source_project.name }} ({{
|
||||||
|
template.source_project.key
|
||||||
|
}}) when you save.
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
This template baseline is rebuilt from its source project when you save.
|
This template baseline is rebuilt from its source
|
||||||
|
project when you save.
|
||||||
</template>
|
</template>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -93,15 +104,25 @@ const submit = (): void => {
|
|||||||
<form class="space-y-6" @submit.prevent="submit">
|
<form class="space-y-6" @submit.prevent="submit">
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<Label for="name">Template name</Label>
|
<Label for="name">Template name</Label>
|
||||||
<Input id="name" v-model="form.name" required placeholder="Foundation template" />
|
<Input
|
||||||
|
id="name"
|
||||||
|
v-model="form.name"
|
||||||
|
required
|
||||||
|
placeholder="Foundation template"
|
||||||
|
/>
|
||||||
<InputError :message="form.errors.name" />
|
<InputError :message="form.errors.name" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<Label for="slug">Template slug</Label>
|
<Label for="slug">Template slug</Label>
|
||||||
<Input id="slug" :model-value="template.slug" disabled />
|
<Input
|
||||||
|
id="slug"
|
||||||
|
:model-value="template.slug"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
The slug stays locked so existing template references remain stable.
|
The slug stays locked so existing template
|
||||||
|
references remain stable.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -109,7 +130,8 @@ const submit = (): void => {
|
|||||||
<div>
|
<div>
|
||||||
<Label>Configuration groups</Label>
|
<Label>Configuration groups</Label>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
Saving rebuilds the selected groups from the current source-project configuration.
|
Saving rebuilds the selected groups from the
|
||||||
|
current source-project configuration.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -126,8 +148,12 @@ const submit = (): void => {
|
|||||||
class="mt-1"
|
class="mt-1"
|
||||||
/>
|
/>
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="font-medium">{{ copyAreaOption.label }}</div>
|
<div class="font-medium">
|
||||||
<p class="text-muted-foreground text-sm">
|
{{ copyAreaOption.label }}
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="text-muted-foreground text-sm"
|
||||||
|
>
|
||||||
{{ copyAreaOption.description }}
|
{{ copyAreaOption.description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -143,19 +169,39 @@ const submit = (): void => {
|
|||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="font-medium">Warning</div>
|
<div class="font-medium">Warning</div>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
Saving this template updates the baseline for {{ linkedProjectCount }} linked {{ linkedProjectCount === 1 ? 'project' : 'projects' }}.
|
Saving this template updates the baseline
|
||||||
Inherited areas will follow the new template version, while existing overrides stay untouched.
|
for {{ linkedProjectCount }} linked
|
||||||
|
{{
|
||||||
|
linkedProjectCount === 1
|
||||||
|
? 'project'
|
||||||
|
: 'projects'
|
||||||
|
}}. Inherited areas will follow the new
|
||||||
|
template version, while existing overrides
|
||||||
|
stay untouched.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Label class="flex cursor-pointer items-start gap-3">
|
<Label
|
||||||
<input v-model="form.confirm_linked_projects_update" type="checkbox" class="mt-1" />
|
class="flex cursor-pointer items-start gap-3"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="
|
||||||
|
form.confirm_linked_projects_update
|
||||||
|
"
|
||||||
|
type="checkbox"
|
||||||
|
class="mt-1"
|
||||||
|
/>
|
||||||
<span class="text-sm">
|
<span class="text-sm">
|
||||||
I understand that linked projects inheriting from this template will be affected.
|
I understand that linked projects inheriting
|
||||||
|
from this template will be affected.
|
||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
<InputError :message="form.errors.confirm_linked_projects_update" />
|
<InputError
|
||||||
|
:message="
|
||||||
|
form.errors.confirm_linked_projects_update
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
@@ -164,7 +210,9 @@ const submit = (): void => {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button as-child variant="outline">
|
<Button as-child variant="outline">
|
||||||
<Link :href="projectTemplatesIndex()">Cancel</Link>
|
<Link :href="projectTemplatesIndex()"
|
||||||
|
>Cancel</Link
|
||||||
|
>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ import {
|
|||||||
edit as projectTemplatesEdit,
|
edit as projectTemplatesEdit,
|
||||||
index as projectTemplatesIndex,
|
index as projectTemplatesIndex,
|
||||||
} from '@/routes/project-templates';
|
} from '@/routes/project-templates';
|
||||||
import { create as projectsCreate, index as projectsIndex } from '@/routes/projects/index';
|
import {
|
||||||
|
create as projectsCreate,
|
||||||
|
index as projectsIndex,
|
||||||
|
} from '@/routes/projects/index';
|
||||||
import type { BreadcrumbItem, ProjectTemplatesIndexPageProps } from '@/types';
|
import type { BreadcrumbItem, ProjectTemplatesIndexPageProps } from '@/types';
|
||||||
|
|
||||||
const props = defineProps<ProjectTemplatesIndexPageProps>();
|
const props = defineProps<ProjectTemplatesIndexPageProps>();
|
||||||
@@ -40,7 +43,9 @@ const removeTemplate = (templateId: number): void => {
|
|||||||
<Head title="Project templates" />
|
<Head title="Project templates" />
|
||||||
|
|
||||||
<div class="flex flex-1 flex-col gap-6 p-4">
|
<div class="flex flex-1 flex-col gap-6 p-4">
|
||||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
<div
|
||||||
|
class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"
|
||||||
|
>
|
||||||
<Heading
|
<Heading
|
||||||
title="Project templates"
|
title="Project templates"
|
||||||
description="Reusable published project templates. Internal project base templates stay hidden here by default."
|
description="Reusable published project templates. Internal project base templates stay hidden here by default."
|
||||||
@@ -64,23 +69,43 @@ const removeTemplate = (templateId: number): void => {
|
|||||||
{{ status }}
|
{{ status }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="templates.length === 0" class="text-muted-foreground text-sm">
|
<div
|
||||||
|
v-if="templates.length === 0"
|
||||||
|
class="text-muted-foreground text-sm"
|
||||||
|
>
|
||||||
No published project templates have been saved yet.
|
No published project templates have been saved yet.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
<div v-else class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||||
<Card v-for="template in templates" :key="template.id" class="h-full">
|
<Card
|
||||||
|
v-for="template in templates"
|
||||||
|
:key="template.id"
|
||||||
|
class="h-full"
|
||||||
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<CardTitle>{{ template.name }}</CardTitle>
|
<CardTitle>{{ template.name }}</CardTitle>
|
||||||
<Badge variant="secondary">{{ template.slug }}</Badge>
|
<Badge variant="secondary">{{
|
||||||
<Badge v-if="template.linked_project_count" variant="outline">
|
template.slug
|
||||||
{{ template.linked_project_count }} linked {{ template.linked_project_count === 1 ? 'project' : 'projects' }}
|
}}</Badge>
|
||||||
|
<Badge
|
||||||
|
v-if="template.linked_project_count"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
{{ template.linked_project_count }} linked
|
||||||
|
{{
|
||||||
|
template.linked_project_count === 1
|
||||||
|
? 'project'
|
||||||
|
: 'projects'
|
||||||
|
}}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<template v-if="template.source_project">
|
<template v-if="template.source_project">
|
||||||
Saved from {{ template.source_project.name }} ({{ template.source_project.key }}).
|
Saved from
|
||||||
|
{{ template.source_project.name }} ({{
|
||||||
|
template.source_project.key
|
||||||
|
}}).
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
Saved template configuration.
|
Saved template configuration.
|
||||||
@@ -106,7 +131,13 @@ const removeTemplate = (templateId: number): void => {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button as-child variant="outline">
|
<Button as-child variant="outline">
|
||||||
<Link :href="projectsCreate({ query: { template: template.id } })">
|
<Link
|
||||||
|
:href="
|
||||||
|
projectsCreate({
|
||||||
|
query: { template: template.id },
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
Use template
|
Use template
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ import AppLayout from '@/layouts/AppLayout.vue';
|
|||||||
import { index as boardIndex } from '@/routes/projects/board';
|
import { index as boardIndex } from '@/routes/projects/board';
|
||||||
import { index as backlogIndex } from '@/routes/projects/backlog';
|
import { index as backlogIndex } from '@/routes/projects/backlog';
|
||||||
import { index, show } from '@/routes/projects/index';
|
import { index, show } from '@/routes/projects/index';
|
||||||
import { create as createTicket, show as showTicket } from '@/routes/projects/tickets';
|
import {
|
||||||
|
create as createTicket,
|
||||||
|
show as showTicket,
|
||||||
|
} from '@/routes/projects/tickets';
|
||||||
import type { BreadcrumbItem, ProjectBacklogPageProps } from '@/types';
|
import type { BreadcrumbItem, ProjectBacklogPageProps } from '@/types';
|
||||||
import type { TicketModel } from '@/types/models/ticket-model';
|
import type { TicketModel } from '@/types/models/ticket-model';
|
||||||
import type { TicketStatusModel } from '@/types/models/ticket-status-model';
|
import type { TicketStatusModel } from '@/types/models/ticket-status-model';
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ import { index as backlogIndex } from '@/routes/projects/backlog';
|
|||||||
import { index as projectIndex, show } from '@/routes/projects/index';
|
import { index as projectIndex, show } from '@/routes/projects/index';
|
||||||
import { board as boardSettings } from '@/routes/projects/settings';
|
import { board as boardSettings } from '@/routes/projects/settings';
|
||||||
import { index as boardIndex } from '@/routes/projects/board';
|
import { index as boardIndex } from '@/routes/projects/board';
|
||||||
import { create as createTicket, show as showTicket } from '@/routes/projects/tickets';
|
import {
|
||||||
|
create as createTicket,
|
||||||
|
show as showTicket,
|
||||||
|
} from '@/routes/projects/tickets';
|
||||||
import type { BreadcrumbItem, ProjectBoardPageProps } from '@/types';
|
import type { BreadcrumbItem, ProjectBoardPageProps } from '@/types';
|
||||||
import type { TicketModel } from '@/types/models/ticket-model';
|
import type { TicketModel } from '@/types/models/ticket-model';
|
||||||
import type { TicketStatusModel } from '@/types/models/ticket-status-model';
|
import type { TicketStatusModel } from '@/types/models/ticket-status-model';
|
||||||
|
|||||||
@@ -31,15 +31,19 @@ const breadcrumbs: BreadcrumbItem[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const defaultTemplateId = props.selectedSourceTemplateId ?? props.sourceTemplates[0]?.id ?? null;
|
const defaultTemplateId =
|
||||||
|
props.selectedSourceTemplateId ?? props.sourceTemplates[0]?.id ?? null;
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
copy_areas: [] as string[],
|
copy_areas: [] as string[],
|
||||||
creation_mode: defaultTemplateId !== null ? 'template' : 'scratch',
|
creation_mode: defaultTemplateId !== null ? 'template' : 'scratch',
|
||||||
key: '',
|
key: '',
|
||||||
name: '',
|
name: '',
|
||||||
source_project_id: props.sourceProjects[0]?.id ? String(props.sourceProjects[0].id) : '',
|
source_project_id: props.sourceProjects[0]?.id
|
||||||
source_template_id: defaultTemplateId !== null ? String(defaultTemplateId) : '',
|
? String(props.sourceProjects[0].id)
|
||||||
|
: '',
|
||||||
|
source_template_id:
|
||||||
|
defaultTemplateId !== null ? String(defaultTemplateId) : '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasSourceProjects = computed(() => props.sourceProjects.length > 0);
|
const hasSourceProjects = computed(() => props.sourceProjects.length > 0);
|
||||||
@@ -48,7 +52,9 @@ const hasSourceTemplates = computed(() => props.sourceTemplates.length > 0);
|
|||||||
const copyAreasError = computed(() => {
|
const copyAreasError = computed(() => {
|
||||||
return (
|
return (
|
||||||
form.errors.copy_areas ??
|
form.errors.copy_areas ??
|
||||||
Object.entries(form.errors).find(([key]) => key.startsWith('copy_areas.'))?.[1] ??
|
Object.entries(form.errors).find(([key]) =>
|
||||||
|
key.startsWith('copy_areas.'),
|
||||||
|
)?.[1] ??
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -93,7 +99,9 @@ const submit = (): void => {
|
|||||||
<div class="grid gap-3">
|
<div class="grid gap-3">
|
||||||
<Label>How do you want to start?</Label>
|
<Label>How do you want to start?</Label>
|
||||||
<div class="grid gap-3 sm:grid-cols-3">
|
<div class="grid gap-3 sm:grid-cols-3">
|
||||||
<Label class="flex cursor-pointer items-start gap-3 rounded-xl border p-4">
|
<Label
|
||||||
|
class="flex cursor-pointer items-start gap-3 rounded-xl border p-4"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
v-model="form.creation_mode"
|
v-model="form.creation_mode"
|
||||||
type="radio"
|
type="radio"
|
||||||
@@ -101,16 +109,25 @@ const submit = (): void => {
|
|||||||
class="mt-1"
|
class="mt-1"
|
||||||
/>
|
/>
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="font-medium">Create from scratch</div>
|
<div class="font-medium">
|
||||||
<p class="text-muted-foreground text-sm">
|
Create from scratch
|
||||||
Start with the default project setup.
|
</div>
|
||||||
|
<p
|
||||||
|
class="text-muted-foreground text-sm"
|
||||||
|
>
|
||||||
|
Start with the default project
|
||||||
|
setup.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
<Label
|
<Label
|
||||||
class="flex items-start gap-3 rounded-xl border p-4"
|
class="flex items-start gap-3 rounded-xl border p-4"
|
||||||
:class="hasSourceProjects ? 'cursor-pointer' : 'cursor-not-allowed opacity-60'"
|
:class="
|
||||||
|
hasSourceProjects
|
||||||
|
? 'cursor-pointer'
|
||||||
|
: 'cursor-not-allowed opacity-60'
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="form.creation_mode"
|
v-model="form.creation_mode"
|
||||||
@@ -120,16 +137,26 @@ const submit = (): void => {
|
|||||||
:disabled="!hasSourceProjects"
|
:disabled="!hasSourceProjects"
|
||||||
/>
|
/>
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="font-medium">Create from existing project</div>
|
<div class="font-medium">
|
||||||
<p class="text-muted-foreground text-sm">
|
Create from existing project
|
||||||
Copy only the settings you explicitly select. Tickets are never duplicated.
|
</div>
|
||||||
|
<p
|
||||||
|
class="text-muted-foreground text-sm"
|
||||||
|
>
|
||||||
|
Copy only the settings you
|
||||||
|
explicitly select. Tickets are never
|
||||||
|
duplicated.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
<Label
|
<Label
|
||||||
class="flex items-start gap-3 rounded-xl border p-4"
|
class="flex items-start gap-3 rounded-xl border p-4"
|
||||||
:class="hasSourceTemplates ? 'cursor-pointer' : 'cursor-not-allowed opacity-60'"
|
:class="
|
||||||
|
hasSourceTemplates
|
||||||
|
? 'cursor-pointer'
|
||||||
|
: 'cursor-not-allowed opacity-60'
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="form.creation_mode"
|
v-model="form.creation_mode"
|
||||||
@@ -139,9 +166,14 @@ const submit = (): void => {
|
|||||||
:disabled="!hasSourceTemplates"
|
:disabled="!hasSourceTemplates"
|
||||||
/>
|
/>
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="font-medium">Create from template</div>
|
<div class="font-medium">
|
||||||
<p class="text-muted-foreground text-sm">
|
Create from template
|
||||||
Reuse a saved configuration template.
|
</div>
|
||||||
|
<p
|
||||||
|
class="text-muted-foreground text-sm"
|
||||||
|
>
|
||||||
|
Reuse a saved configuration
|
||||||
|
template.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Label>
|
</Label>
|
||||||
@@ -172,7 +204,9 @@ const submit = (): void => {
|
|||||||
/>
|
/>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
Example ticket keys will look like
|
Example ticket keys will look like
|
||||||
<span class="text-foreground font-medium">CORE-1</span>.
|
<span class="text-foreground font-medium"
|
||||||
|
>CORE-1</span
|
||||||
|
>.
|
||||||
</p>
|
</p>
|
||||||
<InputError :message="form.errors.key" />
|
<InputError :message="form.errors.key" />
|
||||||
</div>
|
</div>
|
||||||
@@ -182,33 +216,43 @@ const submit = (): void => {
|
|||||||
class="space-y-6 rounded-xl border border-dashed p-4"
|
class="space-y-6 rounded-xl border border-dashed p-4"
|
||||||
>
|
>
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<Label for="source_project_id">Source project</Label>
|
<Label for="source_project_id"
|
||||||
|
>Source project</Label
|
||||||
|
>
|
||||||
<select
|
<select
|
||||||
id="source_project_id"
|
id="source_project_id"
|
||||||
v-model="form.source_project_id"
|
v-model="form.source_project_id"
|
||||||
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
:disabled="!hasSourceProjects"
|
:disabled="!hasSourceProjects"
|
||||||
>
|
>
|
||||||
<option value="">Select a source project</option>
|
<option value="">
|
||||||
|
Select a source project
|
||||||
|
</option>
|
||||||
<option
|
<option
|
||||||
v-for="sourceProject in sourceProjects"
|
v-for="sourceProject in sourceProjects"
|
||||||
:key="sourceProject.id"
|
:key="sourceProject.id"
|
||||||
:value="String(sourceProject.id)"
|
:value="String(sourceProject.id)"
|
||||||
>
|
>
|
||||||
{{ sourceProject.name }} ({{ sourceProject.key }})
|
{{ sourceProject.name }} ({{
|
||||||
|
sourceProject.key
|
||||||
|
}})
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
The new project keeps its own identity, key, and future ticket numbering.
|
The new project keeps its own identity, key,
|
||||||
|
and future ticket numbering.
|
||||||
</p>
|
</p>
|
||||||
<InputError :message="form.errors.source_project_id" />
|
<InputError
|
||||||
|
:message="form.errors.source_project_id"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-3">
|
<div class="grid gap-3">
|
||||||
<div>
|
<div>
|
||||||
<Label>Copy options</Label>
|
<Label>Copy options</Label>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
Choose which settings should be copied into the new project.
|
Choose which settings should be copied
|
||||||
|
into the new project.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -225,8 +269,12 @@ const submit = (): void => {
|
|||||||
class="mt-1"
|
class="mt-1"
|
||||||
/>
|
/>
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="font-medium">{{ copyAreaOption.label }}</div>
|
<div class="font-medium">
|
||||||
<p class="text-muted-foreground text-sm">
|
{{ copyAreaOption.label }}
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="text-muted-foreground text-sm"
|
||||||
|
>
|
||||||
{{ copyAreaOption.description }}
|
{{ copyAreaOption.description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -234,7 +282,8 @@ const submit = (): void => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
Operational ticket data is always left behind so the new project starts clean.
|
Operational ticket data is always left
|
||||||
|
behind so the new project starts clean.
|
||||||
</p>
|
</p>
|
||||||
<InputError :message="copyAreasError" />
|
<InputError :message="copyAreasError" />
|
||||||
</div>
|
</div>
|
||||||
@@ -249,7 +298,7 @@ const submit = (): void => {
|
|||||||
<select
|
<select
|
||||||
id="source_template_id"
|
id="source_template_id"
|
||||||
v-model="form.source_template_id"
|
v-model="form.source_template_id"
|
||||||
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
:disabled="!hasSourceTemplates"
|
:disabled="!hasSourceTemplates"
|
||||||
>
|
>
|
||||||
<option value="">Select a template</option>
|
<option value="">Select a template</option>
|
||||||
@@ -258,21 +307,29 @@ const submit = (): void => {
|
|||||||
:key="sourceTemplate.id"
|
:key="sourceTemplate.id"
|
||||||
:value="String(sourceTemplate.id)"
|
:value="String(sourceTemplate.id)"
|
||||||
>
|
>
|
||||||
{{ sourceTemplate.name }} ({{ sourceTemplate.slug }})
|
{{ sourceTemplate.name }} ({{
|
||||||
|
sourceTemplate.slug
|
||||||
|
}})
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
Templates apply their saved configuration groups to the new project.
|
Templates apply their saved configuration
|
||||||
|
groups to the new project.
|
||||||
</p>
|
</p>
|
||||||
<InputError :message="form.errors.source_template_id" />
|
<InputError
|
||||||
|
:message="form.errors.source_template_id"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else-if="!hasSourceProjects && !hasSourceTemplates"
|
v-else-if="
|
||||||
|
!hasSourceProjects && !hasSourceTemplates
|
||||||
|
"
|
||||||
class="text-muted-foreground text-sm"
|
class="text-muted-foreground text-sm"
|
||||||
>
|
>
|
||||||
No accessible source projects or saved templates are available yet.
|
No accessible source projects or saved templates are
|
||||||
|
available yet.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
|||||||
@@ -117,7 +117,9 @@ const projectAreas = [
|
|||||||
Built from template {{ templateState.templateName }}
|
Built from template {{ templateState.templateName }}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
This project keeps its template link. Inherited areas follow the template baseline; overridden areas were customized locally.
|
This project keeps its template link. Inherited areas
|
||||||
|
follow the template baseline; overridden areas were
|
||||||
|
customized locally.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="flex flex-wrap gap-2">
|
<CardContent class="flex flex-wrap gap-2">
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
buildAcceptanceCriteria,
|
buildAcceptanceCriteria,
|
||||||
formatChecklistLines,
|
formatChecklistLines,
|
||||||
normalizeAcceptanceCriteria,
|
normalizeAcceptanceCriteria,
|
||||||
parseChecklistLines,
|
|
||||||
syncAcceptanceCriteriaDescendantCheckedState,
|
syncAcceptanceCriteriaDescendantCheckedState,
|
||||||
} from '@/lib/acceptance-criteria';
|
} from '@/lib/acceptance-criteria';
|
||||||
import ProjectTicketBranchController from '@/actions/App/Http/Controllers/ProjectTicketBranchController';
|
import ProjectTicketBranchController from '@/actions/App/Http/Controllers/ProjectTicketBranchController';
|
||||||
@@ -123,7 +122,9 @@ const breadcrumbs: BreadcrumbItem[] = [
|
|||||||
const acceptanceCriteriaEditorState = acceptanceCriteriaToEditorState(
|
const acceptanceCriteriaEditorState = acceptanceCriteriaToEditorState(
|
||||||
props.ticket.acceptance_criteria,
|
props.ticket.acceptance_criteria,
|
||||||
);
|
);
|
||||||
const acceptanceCriteriaChecklist = ref(acceptanceCriteriaEditorState.checklist);
|
const acceptanceCriteriaChecklist = ref(
|
||||||
|
acceptanceCriteriaEditorState.checklist,
|
||||||
|
);
|
||||||
const savedAcceptanceCriteria = ref(
|
const savedAcceptanceCriteria = ref(
|
||||||
normalizeAcceptanceCriteria(props.ticket.acceptance_criteria),
|
normalizeAcceptanceCriteria(props.ticket.acceptance_criteria),
|
||||||
);
|
);
|
||||||
@@ -131,7 +132,9 @@ const isPersistingAcceptanceCriteriaToggle = ref(false);
|
|||||||
const acceptanceCriteriaToggleError = ref('');
|
const acceptanceCriteriaToggleError = ref('');
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
acceptance_criteria: buildAcceptanceCriteria(acceptanceCriteriaChecklist.value),
|
acceptance_criteria: buildAcceptanceCriteria(
|
||||||
|
acceptanceCriteriaChecklist.value,
|
||||||
|
),
|
||||||
assignee_user_id: props.ticket.assignee_user_id ?? '',
|
assignee_user_id: props.ticket.assignee_user_id ?? '',
|
||||||
author_user_id: props.ticket.author_user_id ?? '',
|
author_user_id: props.ticket.author_user_id ?? '',
|
||||||
blocked_comment: props.ticket.blocked_comment ?? '',
|
blocked_comment: props.ticket.blocked_comment ?? '',
|
||||||
@@ -157,8 +160,8 @@ const form = useForm({
|
|||||||
story_points: props.ticket.story_points,
|
story_points: props.ticket.story_points,
|
||||||
});
|
});
|
||||||
|
|
||||||
const acceptanceCriteriaPreviewItems = computed(() =>
|
const acceptanceCriteriaPreviewItems = computed(
|
||||||
savedAcceptanceCriteria.value,
|
() => savedAcceptanceCriteria.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasUnsavedAcceptanceCriteriaDraft = computed(
|
const hasUnsavedAcceptanceCriteriaDraft = computed(
|
||||||
@@ -318,8 +321,7 @@ watch(
|
|||||||
|
|
||||||
branchNameValidation.value = {
|
branchNameValidation.value = {
|
||||||
is_valid: false,
|
is_valid: false,
|
||||||
message:
|
message: `${appName} could not validate this branch name right now.`,
|
||||||
`${appName} could not validate this branch name right now.`,
|
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
if (!abortController.signal.aborted) {
|
if (!abortController.signal.aborted) {
|
||||||
@@ -361,7 +363,9 @@ const submitBranchCreation = (): void => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buildAcceptanceCriteriaForSubmit = () => {
|
const buildAcceptanceCriteriaForSubmit = () => {
|
||||||
const draftItems = buildAcceptanceCriteria(acceptanceCriteriaChecklist.value);
|
const draftItems = buildAcceptanceCriteria(
|
||||||
|
acceptanceCriteriaChecklist.value,
|
||||||
|
);
|
||||||
|
|
||||||
return draftItems.map((item, index) => ({
|
return draftItems.map((item, index) => ({
|
||||||
...item,
|
...item,
|
||||||
@@ -371,7 +375,9 @@ const buildAcceptanceCriteriaForSubmit = () => {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildSavedTicketPayload = (acceptanceCriteria = savedAcceptanceCriteria.value) => ({
|
const buildSavedTicketPayload = (
|
||||||
|
acceptanceCriteria = savedAcceptanceCriteria.value,
|
||||||
|
) => ({
|
||||||
acceptance_criteria: acceptanceCriteria,
|
acceptance_criteria: acceptanceCriteria,
|
||||||
assignee_user_id: props.ticket.assignee_user_id,
|
assignee_user_id: props.ticket.assignee_user_id,
|
||||||
author_user_id: props.ticket.author_user_id,
|
author_user_id: props.ticket.author_user_id,
|
||||||
@@ -407,7 +413,9 @@ const persistAcceptanceCriteriaToggle = async (
|
|||||||
index,
|
index,
|
||||||
checked,
|
checked,
|
||||||
);
|
);
|
||||||
const previousChecklist = formatChecklistLines(savedAcceptanceCriteria.value);
|
const previousChecklist = formatChecklistLines(
|
||||||
|
savedAcceptanceCriteria.value,
|
||||||
|
);
|
||||||
const nextChecklist = formatChecklistLines(nextAcceptanceCriteria);
|
const nextChecklist = formatChecklistLines(nextAcceptanceCriteria);
|
||||||
const csrfToken = document
|
const csrfToken = document
|
||||||
.querySelector('meta[name="csrf-token"]')
|
.querySelector('meta[name="csrf-token"]')
|
||||||
@@ -761,14 +769,18 @@ const formatDateTime = (value: string): string => {
|
|||||||
placeholder="[ ] User can sign in [x] Validation is shown for invalid credentials"
|
placeholder="[ ] User can sign in [x] Validation is shown for invalid credentials"
|
||||||
/>
|
/>
|
||||||
<p class="text-muted-foreground text-xs">
|
<p class="text-muted-foreground text-xs">
|
||||||
Use <code>[ ]</code> / <code>[x]</code> for checklist items. Indent with two spaces for nested steps.
|
Use <code>[ ]</code> /
|
||||||
Text changes are saved with the main ticket update.
|
<code>[x]</code> for checklist items.
|
||||||
|
Indent with two spaces for nested steps.
|
||||||
|
Text changes are saved with the main
|
||||||
|
ticket update.
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
v-if="hasUnsavedAcceptanceCriteriaDraft"
|
v-if="hasUnsavedAcceptanceCriteriaDraft"
|
||||||
class="text-muted-foreground text-xs"
|
class="text-muted-foreground text-xs"
|
||||||
>
|
>
|
||||||
Save checklist text changes before toggling completion directly.
|
Save checklist text changes before
|
||||||
|
toggling completion directly.
|
||||||
</p>
|
</p>
|
||||||
<InputError
|
<InputError
|
||||||
:message="acceptanceCriteriaError"
|
:message="acceptanceCriteriaError"
|
||||||
@@ -779,7 +791,10 @@ const formatDateTime = (value: string): string => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="acceptanceCriteriaPreviewItems.length > 0"
|
v-if="
|
||||||
|
acceptanceCriteriaPreviewItems.length >
|
||||||
|
0
|
||||||
|
"
|
||||||
class="border-border rounded-xl border p-4 text-sm"
|
class="border-border rounded-xl border p-4 text-sm"
|
||||||
>
|
>
|
||||||
<p class="mb-3 font-medium">
|
<p class="mb-3 font-medium">
|
||||||
@@ -787,7 +802,9 @@ const formatDateTime = (value: string): string => {
|
|||||||
</p>
|
</p>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
<li
|
<li
|
||||||
v-for="(item, index) in acceptanceCriteriaPreviewItems"
|
v-for="(
|
||||||
|
item, index
|
||||||
|
) in acceptanceCriteriaPreviewItems"
|
||||||
:key="item.id ?? index"
|
:key="item.id ?? index"
|
||||||
:style="{
|
:style="{
|
||||||
marginLeft: `${item.indent * 1.25}rem`,
|
marginLeft: `${item.indent * 1.25}rem`,
|
||||||
@@ -805,7 +822,12 @@ const formatDateTime = (value: string): string => {
|
|||||||
hasUnsavedAcceptanceCriteriaDraft
|
hasUnsavedAcceptanceCriteriaDraft
|
||||||
"
|
"
|
||||||
class="border-input text-primary focus:ring-ring mt-0.5 size-4 rounded"
|
class="border-input text-primary focus:ring-ring mt-0.5 size-4 rounded"
|
||||||
@change="onAcceptanceCriteriaCheckboxChange(index, $event)"
|
@change="
|
||||||
|
onAcceptanceCriteriaCheckboxChange(
|
||||||
|
index,
|
||||||
|
$event,
|
||||||
|
)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
:for="`acceptance-criteria-${item.id ?? index}`"
|
:for="`acceptance-criteria-${item.id ?? index}`"
|
||||||
@@ -1530,10 +1552,7 @@ const formatDateTime = (value: string): string => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card
|
<Card v-if="ticket.ticket_type?.is_parent" class="h-fit">
|
||||||
v-if="ticket.ticket_type?.is_parent"
|
|
||||||
class="h-fit"
|
|
||||||
>
|
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Child tickets</CardTitle>
|
<CardTitle>Child tickets</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
@@ -1572,10 +1591,12 @@ const formatDateTime = (value: string): string => {
|
|||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Link
|
<Link
|
||||||
:href="showTicket({
|
:href="
|
||||||
project: project.id,
|
showTicket({
|
||||||
ticket: currentParentTicket.id,
|
project: project.id,
|
||||||
})"
|
ticket: currentParentTicket.id,
|
||||||
|
})
|
||||||
|
"
|
||||||
class="border-border hover:border-primary/60 block rounded-xl border px-4 py-4 transition"
|
class="border-border hover:border-primary/60 block rounded-xl border px-4 py-4 transition"
|
||||||
>
|
>
|
||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ const returnHref =
|
|||||||
props.source === 'board'
|
props.source === 'board'
|
||||||
? boardIndex(props.project.id)
|
? boardIndex(props.project.id)
|
||||||
: backlogIndex(props.project.id);
|
: backlogIndex(props.project.id);
|
||||||
const returnLabel = props.source === 'board' ? 'Back to board' : 'Back to backlog';
|
const returnLabel =
|
||||||
|
props.source === 'board' ? 'Back to board' : 'Back to backlog';
|
||||||
|
|
||||||
const breadcrumbs: BreadcrumbItem[] = [
|
const breadcrumbs: BreadcrumbItem[] = [
|
||||||
{
|
{
|
||||||
@@ -64,7 +65,9 @@ const hasTicketMetadata =
|
|||||||
const acceptanceCriteriaChecklist = ref('');
|
const acceptanceCriteriaChecklist = ref('');
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
acceptance_criteria: buildAcceptanceCriteria(acceptanceCriteriaChecklist.value),
|
acceptance_criteria: buildAcceptanceCriteria(
|
||||||
|
acceptanceCriteriaChecklist.value,
|
||||||
|
),
|
||||||
assignee_user_id: '',
|
assignee_user_id: '',
|
||||||
description: '',
|
description: '',
|
||||||
parent_ticket_id: '',
|
parent_ticket_id: '',
|
||||||
@@ -92,7 +95,9 @@ const acceptanceCriteriaError = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const submit = (): void => {
|
const submit = (): void => {
|
||||||
form.acceptance_criteria = buildAcceptanceCriteria(acceptanceCriteriaChecklist.value);
|
form.acceptance_criteria = buildAcceptanceCriteria(
|
||||||
|
acceptanceCriteriaChecklist.value,
|
||||||
|
);
|
||||||
|
|
||||||
form.submit(ProjectTicketController.store({ project: props.project.id }), {
|
form.submit(ProjectTicketController.store({ project: props.project.id }), {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
@@ -173,7 +178,9 @@ const ticketStatusLabel = (ticketStatus: TicketStatusModel): string => {
|
|||||||
<InputError :message="form.errors.title" />
|
<InputError :message="form.errors.title" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]">
|
<div
|
||||||
|
class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]"
|
||||||
|
>
|
||||||
<div class="grid gap-6">
|
<div class="grid gap-6">
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<Label for="description">Description</Label>
|
<Label for="description">Description</Label>
|
||||||
@@ -185,30 +192,46 @@ const ticketStatusLabel = (ticketStatus: TicketStatusModel): string => {
|
|||||||
class="border-input bg-background shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-60"
|
class="border-input bg-background shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
placeholder="Describe the expected behavior and relevant context."
|
placeholder="Describe the expected behavior and relevant context."
|
||||||
/>
|
/>
|
||||||
<InputError :message="form.errors.description" />
|
<InputError
|
||||||
|
:message="form.errors.description"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-4">
|
<div class="grid gap-4">
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<Label for="acceptance_criteria_checklist">
|
<Label
|
||||||
|
for="acceptance_criteria_checklist"
|
||||||
|
>
|
||||||
Acceptance criteria checklist
|
Acceptance criteria checklist
|
||||||
</Label>
|
</Label>
|
||||||
<textarea
|
<textarea
|
||||||
id="acceptance_criteria_checklist"
|
id="acceptance_criteria_checklist"
|
||||||
v-model="acceptanceCriteriaChecklist"
|
v-model="
|
||||||
|
acceptanceCriteriaChecklist
|
||||||
|
"
|
||||||
rows="6"
|
rows="6"
|
||||||
:disabled="form.processing"
|
:disabled="form.processing"
|
||||||
class="border-input bg-background shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-60"
|
class="border-input bg-background shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
placeholder="[ ] User can sign in [x] Validation is shown for invalid credentials"
|
placeholder="[ ] User can sign in [x] Validation is shown for invalid credentials"
|
||||||
/>
|
/>
|
||||||
<p class="text-muted-foreground text-xs">
|
<p
|
||||||
Use <code>[ ]</code> / <code>[x]</code> for checklist items. Indent with two spaces for nested steps.
|
class="text-muted-foreground text-xs"
|
||||||
|
>
|
||||||
|
Use <code>[ ]</code> /
|
||||||
|
<code>[x]</code> for checklist
|
||||||
|
items. Indent with two spaces for
|
||||||
|
nested steps.
|
||||||
</p>
|
</p>
|
||||||
<InputError :message="acceptanceCriteriaError" />
|
<InputError
|
||||||
|
:message="acceptanceCriteriaError"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="acceptanceCriteriaPreviewItems.length > 0"
|
v-if="
|
||||||
|
acceptanceCriteriaPreviewItems.length >
|
||||||
|
0
|
||||||
|
"
|
||||||
class="border-border rounded-xl border p-4 text-sm"
|
class="border-border rounded-xl border p-4 text-sm"
|
||||||
>
|
>
|
||||||
<p class="mb-3 font-medium">
|
<p class="mb-3 font-medium">
|
||||||
@@ -216,14 +239,18 @@ const ticketStatusLabel = (ticketStatus: TicketStatusModel): string => {
|
|||||||
</p>
|
</p>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
<li
|
<li
|
||||||
v-for="(item, index) in acceptanceCriteriaPreviewItems"
|
v-for="(
|
||||||
|
item, index
|
||||||
|
) in acceptanceCriteriaPreviewItems"
|
||||||
:key="index"
|
:key="index"
|
||||||
:style="{
|
:style="{
|
||||||
marginLeft: `${item.indent * 1.25}rem`,
|
marginLeft: `${item.indent * 1.25}rem`,
|
||||||
}"
|
}"
|
||||||
class="flex items-start gap-2"
|
class="flex items-start gap-2"
|
||||||
>
|
>
|
||||||
<span class="mt-0.5">{{ item.checked ? '☑' : '☐' }}</span>
|
<span class="mt-0.5">{{
|
||||||
|
item.checked ? '☑' : '☐'
|
||||||
|
}}</span>
|
||||||
<span>{{ item.text }}</span>
|
<span>{{ item.text }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -248,7 +275,9 @@ const ticketStatusLabel = (ticketStatus: TicketStatusModel): string => {
|
|||||||
{{ ticketType.name }}
|
{{ ticketType.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<InputError :message="form.errors.ticket_type_id" />
|
<InputError
|
||||||
|
:message="form.errors.ticket_type_id"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
@@ -269,7 +298,11 @@ const ticketStatusLabel = (ticketStatus: TicketStatusModel): string => {
|
|||||||
{{ ticketPriority.name }}
|
{{ ticketPriority.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<InputError :message="form.errors.ticket_priority_id" />
|
<InputError
|
||||||
|
:message="
|
||||||
|
form.errors.ticket_priority_id
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
@@ -285,10 +318,14 @@ const ticketStatusLabel = (ticketStatus: TicketStatusModel): string => {
|
|||||||
:key="ticketStatus.id"
|
:key="ticketStatus.id"
|
||||||
:value="String(ticketStatus.id)"
|
:value="String(ticketStatus.id)"
|
||||||
>
|
>
|
||||||
{{ ticketStatusLabel(ticketStatus) }}
|
{{
|
||||||
|
ticketStatusLabel(ticketStatus)
|
||||||
|
}}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<InputError :message="form.errors.ticket_status_id" />
|
<InputError
|
||||||
|
:message="form.errors.ticket_status_id"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
@@ -310,7 +347,9 @@ const ticketStatusLabel = (ticketStatus: TicketStatusModel): string => {
|
|||||||
{{ user.name }}
|
{{ user.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<InputError :message="form.errors.assignee_user_id" />
|
<InputError
|
||||||
|
:message="form.errors.assignee_user_id"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
@@ -323,16 +362,24 @@ const ticketStatusLabel = (ticketStatus: TicketStatusModel): string => {
|
|||||||
:disabled="form.processing"
|
:disabled="form.processing"
|
||||||
class="border-input bg-background shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-60"
|
class="border-input bg-background shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
>
|
>
|
||||||
<option value="">No parent ticket</option>
|
<option value="">
|
||||||
|
No parent ticket
|
||||||
|
</option>
|
||||||
<option
|
<option
|
||||||
v-for="parentTicketOption in parentTicketOptions"
|
v-for="parentTicketOption in parentTicketOptions"
|
||||||
:key="parentTicketOption.id"
|
:key="parentTicketOption.id"
|
||||||
:value="String(parentTicketOption.id)"
|
:value="
|
||||||
|
String(parentTicketOption.id)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ `${parentTicketOption.ticket_key} - ${parentTicketOption.title}` }}
|
{{
|
||||||
|
`${parentTicketOption.ticket_key} - ${parentTicketOption.title}`
|
||||||
|
}}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<InputError :message="form.errors.parent_ticket_id" />
|
<InputError
|
||||||
|
:message="form.errors.parent_ticket_id"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -349,7 +396,9 @@ const ticketStatusLabel = (ticketStatus: TicketStatusModel): string => {
|
|||||||
step="1"
|
step="1"
|
||||||
:disabled="form.processing"
|
:disabled="form.processing"
|
||||||
/>
|
/>
|
||||||
<InputError :message="form.errors.story_points" />
|
<InputError
|
||||||
|
:message="form.errors.story_points"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ const breadcrumbItems: BreadcrumbItem[] = [
|
|||||||
/>
|
/>
|
||||||
<p class="text-muted-foreground text-sm">
|
<p class="text-muted-foreground text-sm">
|
||||||
Each user needs their own application key. Without a
|
Each user needs their own application key. Without a
|
||||||
matching personal connection, {{ appName }} can only show
|
matching personal connection, {{ appName }} can only
|
||||||
repository names and will link you back to this page.
|
show repository names and will link you back to this
|
||||||
|
page.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,433 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Mcp;
|
||||||
|
|
||||||
|
use App\Actions\Notifications\DeliverNotificationAction;
|
||||||
|
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
||||||
|
use App\Actions\Tickets\SyncProjectTicketFieldValues;
|
||||||
|
use App\Enums\NotificationEventType;
|
||||||
|
use App\Mcp\Servers\KanbanGitServer;
|
||||||
|
use App\Mcp\Tools\CreateTicket;
|
||||||
|
use App\Models\NotificationAction;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Ticket;
|
||||||
|
use App\Models\TicketFieldDefinition;
|
||||||
|
use App\Models\TicketPriority;
|
||||||
|
use App\Models\TicketStatus;
|
||||||
|
use App\Models\TicketType;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
use Illuminate\Testing\Fluent\AssertableJson;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use RuntimeException;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class CreateTicketTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private Project $project;
|
||||||
|
|
||||||
|
private User $user;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->user = User::factory()->create();
|
||||||
|
$this->project = Project::factory()->create(['key' => 'CORE']);
|
||||||
|
app(InitializeProjectTicketMetadata::class)->handle($this->project);
|
||||||
|
$this->syncProjectPermissions($this->project, $this->user, ['write']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_application_key_can_discover_creation_fields_and_required_inputs(): void
|
||||||
|
{
|
||||||
|
$response = $this->withApplicationKey($this->user)->postJson('/mcp/kanbangit', [
|
||||||
|
'id' => 1,
|
||||||
|
'jsonrpc' => '2.0',
|
||||||
|
'method' => 'tools/list',
|
||||||
|
'params' => [],
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$tool = collect($response->json('result.tools'))->firstWhere('name', 'create_ticket');
|
||||||
|
|
||||||
|
$this->assertNotNull($tool);
|
||||||
|
$this->assertFalse($tool['annotations']['destructiveHint']);
|
||||||
|
$this->assertEqualsCanonicalizing([
|
||||||
|
'project_id', 'title', 'ticket_type_id', 'ticket_status_id', 'ticket_priority_id',
|
||||||
|
], $tool['inputSchema']['required']);
|
||||||
|
$this->assertEqualsCanonicalizing([
|
||||||
|
'project_id', 'title', 'description', 'ticket_type_id', 'ticket_status_id',
|
||||||
|
'ticket_priority_id', 'parent_ticket_id', 'assignee_user_id', 'author_user_id',
|
||||||
|
'story_points', 'is_blocked', 'blocked_comment', 'acceptance_criteria', 'field_values',
|
||||||
|
], array_keys($tool['inputSchema']['properties']));
|
||||||
|
$this->assertSame('integer', Arr::get($tool, 'inputSchema.properties.ticket_type_id.type'));
|
||||||
|
$this->assertSame(['integer', 'null'], Arr::get($tool, 'inputSchema.properties.parent_ticket_id.type'));
|
||||||
|
$this->assertSame('object', Arr::get($tool, 'inputSchema.properties.acceptance_criteria.items.type'));
|
||||||
|
$this->assertSame('object', Arr::get($tool, 'inputSchema.properties.field_values.items.type'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_application_key_can_create_a_child_with_all_editable_fields(): void
|
||||||
|
{
|
||||||
|
$assignee = User::factory()->create();
|
||||||
|
$author = User::factory()->create();
|
||||||
|
$type = TicketType::factory()->for($this->project)->create(['name' => 'Bug', 'is_parent' => false, 'position' => 3]);
|
||||||
|
$status = TicketStatus::factory()->for($this->project)->create(['name' => 'Ready', 'position' => 2]);
|
||||||
|
$priority = TicketPriority::factory()->for($this->project)->create(['name' => 'High', 'position' => 2]);
|
||||||
|
$parent = $this->parentTicket($this->project);
|
||||||
|
$numberField = TicketFieldDefinition::factory()->for($this->project)->create(['field_type' => 'number']);
|
||||||
|
$userField = TicketFieldDefinition::factory()->for($this->project)->create(['field_type' => 'user', 'position' => 2]);
|
||||||
|
$this->syncProjectPermissions($this->project, $this->user, ['admin']);
|
||||||
|
|
||||||
|
$this->withApplicationKey($this->user)->postJson('/mcp/kanbangit', [
|
||||||
|
'id' => 2,
|
||||||
|
'jsonrpc' => '2.0',
|
||||||
|
'method' => 'tools/call',
|
||||||
|
'params' => [
|
||||||
|
'name' => 'create_ticket',
|
||||||
|
'arguments' => $this->arguments([
|
||||||
|
'title' => 'Fix child ticket creation',
|
||||||
|
'description' => 'Created through MCP.',
|
||||||
|
'ticket_type_id' => $type->id,
|
||||||
|
'ticket_status_id' => $status->id,
|
||||||
|
'ticket_priority_id' => $priority->id,
|
||||||
|
'parent_ticket_id' => $parent->id,
|
||||||
|
'assignee_user_id' => $assignee->id,
|
||||||
|
'author_user_id' => $author->id,
|
||||||
|
'story_points' => 0,
|
||||||
|
'is_blocked' => true,
|
||||||
|
'blocked_comment' => 'Waiting for the dependency.',
|
||||||
|
'acceptance_criteria' => [
|
||||||
|
['text' => 'Parent criterion', 'checked' => true, 'indent' => 0],
|
||||||
|
['text' => 'Child criterion', 'checked' => false, 'indent' => 1],
|
||||||
|
],
|
||||||
|
'field_values' => [
|
||||||
|
['ticket_field_definition_id' => $numberField->id, 'value_number' => 2.5],
|
||||||
|
['ticket_field_definition_id' => $userField->id, 'value_user_id' => $assignee->id],
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('result.isError', false)
|
||||||
|
->assertJsonPath('result.structuredContent.meta.message', 'CORE-2 was created.')
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket.ticket_type.id', $type->id)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket.ticket_status.id', $status->id)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket.ticket_priority.id', $priority->id)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket.parent_ticket.id', $parent->id)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket.author.id', $author->id)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket.assignee.id', $assignee->id)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket.is_blocked', true)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket.acceptance_criteria.1.indent', 1)
|
||||||
|
->assertJsonCount(2, 'result.structuredContent.data.ticket.field_values');
|
||||||
|
|
||||||
|
$ticket = $this->project->tickets()->where('ticket_key', 'CORE-2')->firstOrFail();
|
||||||
|
$this->assertSame('Fix child ticket creation', $ticket->title);
|
||||||
|
$this->assertSame('Created through MCP.', $ticket->description);
|
||||||
|
$this->assertSame(0, $ticket->story_points);
|
||||||
|
$this->assertSame('Waiting for the dependency.', $ticket->blocked_comment);
|
||||||
|
$this->assertSame($parent->id, $ticket->parent_ticket_id);
|
||||||
|
$this->assertSame($type->id, $ticket->ticket_type_id);
|
||||||
|
$this->assertSame(2.5, $ticket->fieldValues()->where('ticket_field_definition_id', $numberField->id)->firstOrFail()->value_number);
|
||||||
|
$this->assertSame($assignee->id, $ticket->fieldValues()->where('ticket_field_definition_id', $userField->id)->firstOrFail()->value_user_id);
|
||||||
|
$criteria = $ticket->acceptanceCriteria()->get()->keyBy('text');
|
||||||
|
$this->assertTrue($criteria['Parent criterion']->is_checked);
|
||||||
|
$this->assertFalse($criteria['Child criterion']->is_checked);
|
||||||
|
$this->assertSame($criteria['Parent criterion']->id, $criteria['Child criterion']->parent_acceptance_criterion_id);
|
||||||
|
$this->assertDatabaseCount('ticket_worklog_entries', 1);
|
||||||
|
$this->assertDatabaseHas('ticket_worklog_entries', [
|
||||||
|
'ticket_id' => $ticket->id,
|
||||||
|
'actor_id' => $this->user->id,
|
||||||
|
'entry_type' => 'created',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_minimal_creation_defaults_optional_fields_and_generates_identity(): void
|
||||||
|
{
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments([
|
||||||
|
'ticket_key' => 'FORGED-999',
|
||||||
|
'sequence_number' => 999,
|
||||||
|
'id' => 999,
|
||||||
|
]))
|
||||||
|
->assertOk()
|
||||||
|
->assertStructuredContent(fn (AssertableJson $json) => $json
|
||||||
|
->where('data.ticket.ticket_key', 'CORE-1')
|
||||||
|
->where('data.ticket.sequence_number', 1)
|
||||||
|
->where('data.ticket.author_user_id', $this->user->id)
|
||||||
|
->where('data.ticket.assignee_user_id', null)
|
||||||
|
->where('data.ticket.parent_ticket_id', null)
|
||||||
|
->where('data.ticket.description', null)
|
||||||
|
->where('data.ticket.story_points', null)
|
||||||
|
->where('data.ticket.is_blocked', false)
|
||||||
|
->where('data.ticket.blocked_comment', null)
|
||||||
|
->where('data.ticket.acceptance_criteria', [])
|
||||||
|
->where('data.ticket.field_values', [])
|
||||||
|
->etc());
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('tickets', 1);
|
||||||
|
$this->assertDatabaseMissing('tickets', ['id' => 999]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_admin_can_create_without_an_author_and_with_nullable_fields(): void
|
||||||
|
{
|
||||||
|
$this->syncProjectPermissions($this->project, $this->user, ['admin']);
|
||||||
|
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments([
|
||||||
|
'author_user_id' => null,
|
||||||
|
'assignee_user_id' => null,
|
||||||
|
'parent_ticket_id' => null,
|
||||||
|
'description' => null,
|
||||||
|
'story_points' => null,
|
||||||
|
'acceptance_criteria' => null,
|
||||||
|
'field_values' => [],
|
||||||
|
'is_blocked' => false,
|
||||||
|
'blocked_comment' => 'Discard this when unblocked.',
|
||||||
|
]))
|
||||||
|
->assertOk()
|
||||||
|
->assertStructuredContent(fn (AssertableJson $json) => $json
|
||||||
|
->where('data.ticket.author_user_id', null)
|
||||||
|
->where('data.ticket.blocked_comment', null)
|
||||||
|
->where('data.ticket.acceptance_criteria', [])
|
||||||
|
->etc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_parent_enabled_tickets_can_also_be_children_and_checklists_use_defaults(): void
|
||||||
|
{
|
||||||
|
$parent = $this->parentTicket($this->project);
|
||||||
|
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments([
|
||||||
|
'ticket_type_id' => $parent->ticket_type_id,
|
||||||
|
'parent_ticket_id' => $parent->id,
|
||||||
|
'author_user_id' => $this->user->id,
|
||||||
|
'acceptance_criteria' => [['text' => ' A criterion ']],
|
||||||
|
]))
|
||||||
|
->assertOk()
|
||||||
|
->assertStructuredContent(fn (AssertableJson $json) => $json
|
||||||
|
->where('data.ticket.ticket_type.is_parent', true)
|
||||||
|
->where('data.ticket.parent_ticket_id', $parent->id)
|
||||||
|
->where('data.ticket.acceptance_criteria.0.text', 'A criterion')
|
||||||
|
->where('data.ticket.acceptance_criteria.0.checked', false)
|
||||||
|
->where('data.ticket.acceptance_criteria.0.indent', 0)
|
||||||
|
->etc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_creation_requires_authentication_and_project_write_permission(): void
|
||||||
|
{
|
||||||
|
KanbanGitServer::tool(CreateTicket::class, $this->arguments())
|
||||||
|
->assertHasErrors(['Authentication required.']);
|
||||||
|
|
||||||
|
foreach ([[], ['view']] as $permissions) {
|
||||||
|
$this->syncProjectPermissions($this->project, $this->user, $permissions);
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments())
|
||||||
|
->assertHasErrors(['cannot update tickets']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('tickets', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_non_admin_cannot_override_or_remove_the_author(): void
|
||||||
|
{
|
||||||
|
$author = User::factory()->create();
|
||||||
|
|
||||||
|
foreach ([$author->id, null] as $authorId) {
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments([
|
||||||
|
'author_user_id' => $authorId,
|
||||||
|
]))->assertHasErrors(['Only project admins can change the author.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('tickets', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_creation_rejects_foreign_project_metadata_and_parent(): void
|
||||||
|
{
|
||||||
|
$otherProject = Project::factory()->create();
|
||||||
|
app(InitializeProjectTicketMetadata::class)->handle($otherProject);
|
||||||
|
$parent = $this->parentTicket($otherProject);
|
||||||
|
$field = TicketFieldDefinition::factory()->for($otherProject)->create(['field_type' => 'number']);
|
||||||
|
$this->syncProjectPermissions($otherProject, $this->user, ['write']);
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
['ticket_type_id' => $otherProject->ticketTypes()->firstOrFail()->id],
|
||||||
|
['ticket_status_id' => $otherProject->ticketStatuses()->firstOrFail()->id],
|
||||||
|
['ticket_priority_id' => $otherProject->ticketPriorities()->firstOrFail()->id],
|
||||||
|
['parent_ticket_id' => $parent->id],
|
||||||
|
['field_values' => [['ticket_field_definition_id' => $field->id, 'value_number' => 1]]],
|
||||||
|
] as $attributes) {
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments($attributes))
|
||||||
|
->assertHasErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame(0, $this->project->tickets()->count());
|
||||||
|
$this->assertDatabaseCount('ticket_worklog_entries', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_creation_rejects_a_parent_without_a_parent_enabled_type(): void
|
||||||
|
{
|
||||||
|
$parent = $this->parentTicket($this->project);
|
||||||
|
$parent->update(['ticket_type_id' => $this->project->ticketTypes()->where('is_parent', false)->firstOrFail()->id]);
|
||||||
|
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments([
|
||||||
|
'parent_ticket_id' => $parent->id,
|
||||||
|
]))->assertHasErrors(['Only tickets with a parent-enabled type']);
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('tickets', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('invalidArguments')]
|
||||||
|
public function test_invalid_arguments_do_not_create_a_ticket(array $attributes): void
|
||||||
|
{
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments($attributes))
|
||||||
|
->assertHasErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('tickets', 0);
|
||||||
|
$this->assertDatabaseCount('acceptance_criteria', 0);
|
||||||
|
$this->assertDatabaseCount('ticket_field_values', 0);
|
||||||
|
$this->assertDatabaseCount('ticket_worklog_entries', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array{array<string, mixed>}>
|
||||||
|
*/
|
||||||
|
public static function invalidArguments(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'missing project' => [['project_id' => null]],
|
||||||
|
'unknown project' => [['project_id' => 999999]],
|
||||||
|
'invalid project' => [['project_id' => 'invalid']],
|
||||||
|
'missing title' => [['title' => null]],
|
||||||
|
'blank title' => [['title' => ' ']],
|
||||||
|
'long title' => [['title' => str_repeat('a', 256)]],
|
||||||
|
'invalid description' => [['description' => []]],
|
||||||
|
'missing type' => [['ticket_type_id' => null]],
|
||||||
|
'unknown type' => [['ticket_type_id' => 999999]],
|
||||||
|
'missing status' => [['ticket_status_id' => null]],
|
||||||
|
'unknown status' => [['ticket_status_id' => 999999]],
|
||||||
|
'missing priority' => [['ticket_priority_id' => null]],
|
||||||
|
'unknown priority' => [['ticket_priority_id' => 999999]],
|
||||||
|
'unknown parent' => [['parent_ticket_id' => 999999]],
|
||||||
|
'unknown assignee' => [['assignee_user_id' => 999999]],
|
||||||
|
'unknown author' => [['author_user_id' => 999999]],
|
||||||
|
'negative estimate' => [['story_points' => -1]],
|
||||||
|
'fractional estimate' => [['story_points' => 1.5]],
|
||||||
|
'invalid blocked flag' => [['is_blocked' => 'yes']],
|
||||||
|
'missing blocker reason' => [['is_blocked' => true]],
|
||||||
|
'blank blocker reason' => [['is_blocked' => true, 'blocked_comment' => ' ']],
|
||||||
|
'invalid checklist' => [['acceptance_criteria' => 'text']],
|
||||||
|
'blank checklist text' => [['acceptance_criteria' => [['text' => ' ']]]],
|
||||||
|
'invalid checklist flag' => [['acceptance_criteria' => [['text' => 'Test', 'checked' => 'yes']]]],
|
||||||
|
'invalid checklist indent' => [['acceptance_criteria' => [['text' => 'Test', 'indent' => 9]]]],
|
||||||
|
'unknown field' => [['field_values' => [['ticket_field_definition_id' => 999999, 'value_number' => 1]]]],
|
||||||
|
'invalid fields' => [['field_values' => 'text']],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_custom_fields_reject_duplicate_definitions_and_incompatible_or_invalid_values(): void
|
||||||
|
{
|
||||||
|
$numberField = TicketFieldDefinition::factory()->for($this->project)->create(['field_type' => 'number']);
|
||||||
|
$userField = TicketFieldDefinition::factory()->for($this->project)->create(['field_type' => 'user', 'position' => 2]);
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
[['ticket_field_definition_id' => $numberField->id, 'value_number' => 1], ['ticket_field_definition_id' => $numberField->id, 'value_number' => 2]],
|
||||||
|
[['ticket_field_definition_id' => $numberField->id, 'value_user_id' => $this->user->id]],
|
||||||
|
[['ticket_field_definition_id' => $userField->id, 'value_number' => 2]],
|
||||||
|
[['ticket_field_definition_id' => $numberField->id, 'value_number' => 'invalid']],
|
||||||
|
[['ticket_field_definition_id' => $userField->id, 'value_user_id' => 999999]],
|
||||||
|
] as $fieldValues) {
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments([
|
||||||
|
'field_values' => $fieldValues,
|
||||||
|
]))->assertHasErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('tickets', 0);
|
||||||
|
$this->assertDatabaseCount('ticket_field_values', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_zero_and_empty_custom_field_values_are_supported(): void
|
||||||
|
{
|
||||||
|
$numberField = TicketFieldDefinition::factory()->for($this->project)->create(['field_type' => 'number']);
|
||||||
|
$userField = TicketFieldDefinition::factory()->for($this->project)->create(['field_type' => 'user', 'position' => 2]);
|
||||||
|
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments([
|
||||||
|
'field_values' => [
|
||||||
|
['ticket_field_definition_id' => $numberField->id, 'value_number' => 0],
|
||||||
|
['ticket_field_definition_id' => $userField->id, 'value_user_id' => null],
|
||||||
|
],
|
||||||
|
]))->assertOk();
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('ticket_field_values', 1);
|
||||||
|
$this->assertDatabaseHas('ticket_field_values', [
|
||||||
|
'ticket_field_definition_id' => $numberField->id,
|
||||||
|
'value_number' => 0,
|
||||||
|
'value_user_id' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_mcp_creation_queues_one_creation_notification_with_the_key_owner_as_actor(): void
|
||||||
|
{
|
||||||
|
$notificationAction = NotificationAction::factory()->webhook()->for($this->project)->create([
|
||||||
|
'event_type' => NotificationEventType::TicketCreate,
|
||||||
|
]);
|
||||||
|
Queue::fake();
|
||||||
|
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments())->assertOk();
|
||||||
|
|
||||||
|
Queue::assertPushed(DeliverNotificationAction::class, 1);
|
||||||
|
Queue::assertPushed(DeliverNotificationAction::class, fn (DeliverNotificationAction $job): bool => $job->notificationActionId === $notificationAction->id
|
||||||
|
&& $job->event->payload()->actorId === $this->user->id
|
||||||
|
&& $job->event->payload()->ticketKey === 'CORE-1'
|
||||||
|
&& $job->event->eventType() === NotificationEventType::TicketCreate);
|
||||||
|
$this->assertDatabaseCount('notification_delivery_logs', 1);
|
||||||
|
$this->assertDatabaseCount('ticket_worklog_entries', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_creation_rolls_back_if_custom_field_persistence_fails(): void
|
||||||
|
{
|
||||||
|
$field = TicketFieldDefinition::factory()->for($this->project)->create(['field_type' => 'number']);
|
||||||
|
$this->mock(SyncProjectTicketFieldValues::class)
|
||||||
|
->shouldReceive('handle')->once()->andThrow(new RuntimeException('Unable to save fields.'));
|
||||||
|
$this->expectException(RuntimeException::class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
KanbanGitServer::actingAs($this->user)->tool(CreateTicket::class, $this->arguments([
|
||||||
|
'acceptance_criteria' => [['text' => 'Criterion']],
|
||||||
|
'field_values' => [['ticket_field_definition_id' => $field->id, 'value_number' => 1]],
|
||||||
|
]));
|
||||||
|
} finally {
|
||||||
|
$this->assertDatabaseCount('tickets', 0);
|
||||||
|
$this->assertDatabaseCount('acceptance_criteria', 0);
|
||||||
|
$this->assertDatabaseCount('ticket_field_values', 0);
|
||||||
|
$this->assertDatabaseCount('ticket_worklog_entries', 0);
|
||||||
|
$this->assertDatabaseCount('notification_delivery_logs', 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $overrides
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function arguments(array $overrides = []): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'project_id' => $this->project->id,
|
||||||
|
'title' => 'MCP ticket',
|
||||||
|
'ticket_type_id' => $this->project->ticketTypes()->where('is_parent', false)->firstOrFail()->id,
|
||||||
|
'ticket_status_id' => $this->project->ticketStatuses()->firstOrFail()->id,
|
||||||
|
'ticket_priority_id' => $this->project->ticketPriorities()->firstOrFail()->id,
|
||||||
|
...$overrides,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parentTicket(Project $project): Ticket
|
||||||
|
{
|
||||||
|
return Ticket::factory()->create([
|
||||||
|
'project_id' => $project->id,
|
||||||
|
'sequence_number' => 1,
|
||||||
|
'ticket_key' => "{$project->key}-1",
|
||||||
|
'ticket_type_id' => $project->ticketTypes()->where('is_parent', true)->firstOrFail()->id,
|
||||||
|
'ticket_status_id' => $project->ticketStatuses()->firstOrFail()->id,
|
||||||
|
'ticket_priority_id' => $project->ticketPriorities()->firstOrFail()->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Mcp;
|
||||||
|
|
||||||
|
use App\Mcp\Servers\KanbanGitServer;
|
||||||
|
use App\Mcp\Tools\ListTicketTypes;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\TicketType;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Testing\Fluent\AssertableJson;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class ListTicketTypesTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_application_key_can_discover_the_read_only_tool_and_required_project_input(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this->withApplicationKey($user)->postJson('/mcp/kanbangit', [
|
||||||
|
'id' => 1,
|
||||||
|
'jsonrpc' => '2.0',
|
||||||
|
'method' => 'tools/list',
|
||||||
|
'params' => [],
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$tool = collect($response->json('result.tools'))->firstWhere('name', 'list_ticket_types');
|
||||||
|
|
||||||
|
$this->assertNotNull($tool);
|
||||||
|
$this->assertTrue($tool['annotations']['readOnlyHint']);
|
||||||
|
$this->assertSame(['project_id'], $tool['inputSchema']['required']);
|
||||||
|
$this->assertSame('integer', $tool['inputSchema']['properties']['project_id']['type']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_project_viewer_can_list_all_project_types_in_configured_order_over_http(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$project = Project::factory()->create();
|
||||||
|
$otherProject = Project::factory()->create();
|
||||||
|
$taskType = TicketType::factory()->for($project)->create([
|
||||||
|
'name' => 'Task',
|
||||||
|
'is_parent' => false,
|
||||||
|
'position' => 2,
|
||||||
|
]);
|
||||||
|
$parentType = TicketType::factory()->for($project)->create([
|
||||||
|
'name' => 'Release',
|
||||||
|
'is_parent' => true,
|
||||||
|
'position' => 1,
|
||||||
|
]);
|
||||||
|
TicketType::factory()->for($otherProject)->create(['name' => 'Foreign type']);
|
||||||
|
$this->syncProjectPermissions($project, $user, ['view']);
|
||||||
|
$this->syncProjectPermissions($otherProject, $user, ['view']);
|
||||||
|
|
||||||
|
$this->withApplicationKey($user)->postJson('/mcp/kanbangit', [
|
||||||
|
'id' => 2,
|
||||||
|
'jsonrpc' => '2.0',
|
||||||
|
'method' => 'tools/call',
|
||||||
|
'params' => [
|
||||||
|
'name' => 'list_ticket_types',
|
||||||
|
'arguments' => ['project_id' => $project->id],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('result.isError', false)
|
||||||
|
->assertJsonPath('result.structuredContent.data.project.id', $project->id)
|
||||||
|
->assertJsonCount(2, 'result.structuredContent.data.ticket_types')
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket_types.0.id', $parentType->id)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket_types.0.name', 'Release')
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket_types.0.is_parent', true)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket_types.0.position', 1)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket_types.0.project_id', $project->id)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket_types.1.id', $taskType->id)
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket_types.1.name', 'Task')
|
||||||
|
->assertJsonPath('result.structuredContent.data.ticket_types.1.is_parent', false);
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('ticket_types', 3);
|
||||||
|
$this->assertDatabaseCount('tickets', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_invisible_project_types_are_not_accessible(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$project = Project::factory()->create();
|
||||||
|
TicketType::factory()->for($project)->create();
|
||||||
|
|
||||||
|
KanbanGitServer::actingAs($user)
|
||||||
|
->tool(ListTicketTypes::class, ['project_id' => $project->id])
|
||||||
|
->assertHasErrors(['You cannot view this project.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_missing_project_returns_an_actionable_error(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
KanbanGitServer::actingAs($user)
|
||||||
|
->tool(ListTicketTypes::class, ['project_id' => 999999])
|
||||||
|
->assertHasErrors(['Project was not found.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_listing_requires_authentication(): void
|
||||||
|
{
|
||||||
|
$project = Project::factory()->create();
|
||||||
|
|
||||||
|
KanbanGitServer::tool(ListTicketTypes::class, ['project_id' => $project->id])
|
||||||
|
->assertHasErrors(['Authentication required.']);
|
||||||
|
|
||||||
|
$this->postJson('/mcp/kanbangit', [
|
||||||
|
'id' => 1,
|
||||||
|
'jsonrpc' => '2.0',
|
||||||
|
'method' => 'tools/call',
|
||||||
|
'params' => [
|
||||||
|
'name' => 'list_ticket_types',
|
||||||
|
'arguments' => ['project_id' => $project->id],
|
||||||
|
],
|
||||||
|
])->assertUnauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_project_without_types_returns_an_empty_list(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$project = Project::factory()->create();
|
||||||
|
$this->syncProjectPermissions($project, $user, ['view']);
|
||||||
|
|
||||||
|
KanbanGitServer::actingAs($user)
|
||||||
|
->tool(ListTicketTypes::class, ['project_id' => $project->id])
|
||||||
|
->assertOk()
|
||||||
|
->assertStructuredContent(fn (AssertableJson $json) => $json
|
||||||
|
->where('data.project.id', $project->id)
|
||||||
|
->where('data.ticket_types', [])
|
||||||
|
->etc());
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('ticket_types', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $arguments
|
||||||
|
*/
|
||||||
|
#[DataProvider('invalidProjectArguments')]
|
||||||
|
public function test_invalid_project_arguments_are_rejected(array $arguments): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
KanbanGitServer::actingAs($user)
|
||||||
|
->tool(ListTicketTypes::class, $arguments)
|
||||||
|
->assertHasErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array{array<string, mixed>}>
|
||||||
|
*/
|
||||||
|
public static function invalidProjectArguments(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'missing project' => [[]],
|
||||||
|
'null project' => [['project_id' => null]],
|
||||||
|
'project name instead of ID' => [['project_id' => 'CORE']],
|
||||||
|
'fractional ID' => [['project_id' => 1.5]],
|
||||||
|
'array instead of ID' => [['project_id' => [1]]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user