# Projects (https://docs.vitrinka.ai/pm/projects)



## One product, one project [#idea]

A project is where a product's work lands: its tasks, boards, recorded sessions, reports and releases. `vitrinka setup` files a repository under a project on its first run; `vitrinka project link [project]` files another checkout under an existing one — a multi-repository project's next repository — and `vitrinka project unlink` drops a checkout again, taking its line out of the project's repositories.

**Settings → Projects** lists every project of the workspace. Opening one shows its name, one line of state, and five tabs: Overview · Routing · Repositories · Fields · Review. Overview, Routing and Repositories are drafts with their own Save; Fields and Review save each row as you change it.

## Overview [#overview]

The title the sidebar shows — the slug is the address and never changes — and the status: **active**, a product, or **system**, plumbing hidden from the sidebar. A project merged into another is read-only here; merged or moved, it points at its new home.

The **icon** leads the tab: the project's launchpad tile, its Work rail row, its crumb and the browser tab of its own pages wear it. **Fetch from site** reads it from the project's production domain or an address you paste — public sites only, and never on an air-gapped install; &#x2A;*Upload…** takes a PNG, JPEG, WebP, GIF, ICO or SVG file; `vitrinka setup` sends the repository's own icon without reaching any site. A workspace without a logo of its own adopts the first project icon, and an admin's choice under Settings → Workspace always wins. Scripts use `PUT /api/v1/projects/{project}/icon` with the image as the body.

## Routing [#routing]

Which recorded sessions land in this project. **Domains** match a browser tab's host by pattern and tag the session with the rule's environment; **App ids** match an Expo app's bundle id, and an app id another project already owns is refused. A domain whose environment is production (`production`, `prod`, `prd` or `live`) also shows the **in-app recorder** switch for the [web recorder](/qa/recorder/web#link). Scripts write the same rules with `PUT /api/v1/projects/{project}/settings` — see [the browser extension](/qa/extension#domains).

## Repositories [#repositories]

The GitHub repositories the project's code lives in. The first is the default for commit pills, the code viewer and release sweeps; a new [release track](/pm/release#rolling) takes all of them. The workspace's GitHub App is the credential and this list is the binding: rows complete from what the connected accounts reach, and a github.com repository none of them can read gets a warning, never a block, so self-hosted git stays legal. A task key in a merged pull request from a repository outside this list links the task but never completes it ([rules](/pm/rules#builtins)).

## Fields [#fields]

Typed custom fields for the project's tasks — text, long text, select, multi-select, checklist, number, date, URL or a relation to another task — each for one task type or all, grouped by the types they apply to. Below them, **Type faces** sets the glyph each task type wears on this project. Defining fields is an admin's job, here or from a terminal; the values live on the tasks, set in a task's properties or with `vitrinka task field set`. Select fields become filter facets. A field's key and kind never change, and removing a field deletes every value stored under it.

```bash
vitrinka project fields add customer "Customer" --kind select --options acme,globex --applies-to bug
vitrinka task field set 418 customer acme
```

## Review [#review]

The project's layer of the review rubric: every rule the project inherits from the built-ins and the workspace, with an enable switch, an editor and a reset to the layer below. The rubric itself is on [the review toolkit](/qa/review-toolkit#brief).

## Merging two projects [#merge]

**merge into…** at the head of a project's settings folds it into another project of the same workspace. Everything moves — tasks with their history, boards, sets, sessions, reports, releases and project memory — and the old name becomes an alias of the home, so every old link keeps working. The dialog previews first (a dry run whose report is the merge's own transaction, rolled back), then merges behind a confirm. A custom field whose kind clashes arrives renamed `<key>-from-<old name>` with its values kept, and the source's rolling release folds into the target's.

Merging, preview included, is a workspace owner's call made as a person; agent tokens are refused and pointed at the CLI. To move only some tasks, change their project through the task doors instead — a subtree moves whole.

```bash
vitrinka project merge old-app --into app           # the dry-run report
vitrinka project merge old-app --into app --apply
vitrinka project merge-plan --into app              # stray projects a merge would fold in
```

## Moving a project to another workspace [#transfer]

`vitrinka project transfer [project] --to <workspace>` copies everything a project owns into another workspace you own — boards with their cards and annotations, sets, sessions, reports, releases and the whole task engine, with every blob they reference. It needs an owner of both workspaces, never an agent token. Dry-run first; a re-run skips what already arrived. A board slug or set key the destination already holds is refused unless `--merge` folds the project into a same-named one there, and the source stays untouched unless `--archive-source` retires it once the copy has committed. Agents use `transfer_project`.

## Deploy marks [#deploys]

`vitrinka project deploy-mark <env> <sha>` records that a commit reached an environment — the fact a todo waiting on `deployed <env>` fires on ([todos that ripen on their own](/use/day#todos)). Run it from the deploy pipeline with an admin or pipeline credential; a member's credential is refused. Agents use `mark_deploy`.

A `prod` or `production` mark also ships work in [History](/pm/history). With the GitHub App connected, it ships a task's PR or commit only when the marked commit contains it, so a hotfix deployed from an older tag never claims the work merged to the main branch after it forked, while its own commits ship with it. The mark's answer reports `lineage`: `ancestry` with the repository and how many waiting refs it contained or excluded, or `time` and the reason (no GitHub, a commit in no bound repository, GitHub too slow), in which case the work merged before the deploy ships with it. A merged PR recorded before vitrinka stored merge commits is weighed by ancestry too: the mark reads its merge commit from one listing of the PRs merged since the deploy (or its fork) landed, and `lineage.filled` counts them. When that listing fails, runs out of time or stops at its page cap, the answer stays `ancestry` but is partial: `lineage.unfilled` counts the PRs it could not place, which ship as under `time` (with the deploy when merged before it), and `lineage.reason` says why.
