# `PhoenixKit.Migrations.Postgres.V112`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.165/lib/phoenix_kit/migrations/postgres/v112.ex#L1)

V112: Project lifecycle + translations + drop unique-name indexes.

Three related changes to the `phoenix_kit_projects*` tables:

## 1. `archived_at` on `phoenix_kit_projects`

Replaces the dual-purpose `status` field (which held both lifecycle
state and a soft-hide flag) with a dedicated nullable timestamp.
Mirrors the workspace convention used by `phoenix_kit_publishing`'s
`posts.trashed_at` and `phoenix_kit_files.trashed_at` — null = visible,
non-null = soft-hidden, with the timestamp doubling as audit metadata.

The `status` column is kept (intentional — see
`phoenix_kit_projects/AGENTS.md`) so a future workflow concept that
legitimately wants a string lifecycle state (e.g. "paused", "blocked",
"on_hold") can reuse the column without another migration. Application
code stops reading or writing it; existing rows whose `status` is
`"archived"` get backfilled into `archived_at` so the dashboard
filters keep working transparently.

## 2. `translations` JSONB on the three project tables

Adds `translations JSONB NOT NULL DEFAULT '{}'` to:

  * `phoenix_kit_projects` (Project — translatable: `name`, `description`)
  * `phoenix_kit_project_tasks` (Task — translatable: `title`, `description`)
  * `phoenix_kit_project_assignments` (Assignment — translatable:
    `description`)

Storage shape mirrors the entities-module "settings translations"
pattern from `PhoenixKitWeb.Components.MultilangForm`'s
`<.translatable_field>` (the variant where `secondary_name` and
`lang_data_key` are passed explicitly):

    %{
      "es-ES" => %{"name" => "Proyecto", "description" => "..."},
      "fr-FR" => %{"name" => "Projet"}
    }

The primary-language values stay in their existing columns (`name`,
`title`, `description`) — the JSONB only holds secondary-language
overrides. Empty/missing override falls back to the primary value at
render time. No primary-language marker key (`_primary_language`) is
needed because the primary lives outside the JSONB.

## 3. Drop unique-name indexes on projects + tasks

V105 split `phoenix_kit_projects_name_index` into two partial unique
indexes (one per `is_template`). V112 drops both of those plus the
unique-title index on `phoenix_kit_project_tasks` because user-input
display names are policy, not structure: code references resources
by `uuid`, so duplicate names across projects/templates/tasks is
fine and the unique constraint just made common workflows (clone
twice, two teams' "Onboarding" templates) raise a constraint error.

Indexes removed:

  * `phoenix_kit_projects_name_template_index` (V105)
  * `phoenix_kit_projects_name_project_index` (V105)
  * `phoenix_kit_project_tasks_title_index` (V101)

## 4. Retype `scheduled_start_date` from `date` to `timestamp(0)`

The "scheduled start" field originally held only a date — fine for
the daily-cadence projects, awkward for "this campaign starts at
09:00 sharp" or "the announcement at 14:30." V112 promotes it to
`timestamp(0)` so the form / popup can carry hour-and-minute
precision. Existing date values are preserved at midnight UTC.

The column name is kept (`scheduled_start_date`) — renaming to
`scheduled_start_at` would force every call site, the changeset
cast list, and any in-flight URL params to chase. Lying name +
honest type beats a churn pass; future cleanup can rename when a
larger refactor is on the table.

## 5. Add `position` to `phoenix_kit_project_tasks` and `phoenix_kit_projects`

Drives manual reorder of the task library, project list, and
template list views. NOT NULL with a default of `0`; existing rows
fold into the same `0` bucket and the schema's secondary
order-by-`inserted_at` kicks in until the user actually drags. New
rows should be inserted via `next_task_position/0` /
`next_project_position/1` so they land at the bottom of their
bucket.

`phoenix_kit_projects.position` is interpreted per `is_template`
scope — projects and templates share the same column but order
independently (the LV sorts within `is_template = false` for the
project list, `is_template = true` for the template list).

Idempotent: re-running is a no-op once the columns + indexes are in
the post-V112 shape.

# `down`

# `up`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
