# `MishkaCmsCore.Activity.ActivityEntry`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/activity/activity_entry.ex#L5)

Synthetic, read-only resource representing one unified activity-log entry, and
the gervaz timeline table over it.

No database table — `Ash.DataLayer.Simple` plus a single `manual` read whose
`manual_read/2` delegates to `MishkaCmsCore.Activity.Query`, which merges every
AshPaperTrail version table + AshEvents event log + Oban jobs in ONE DB-side
UNION. Attributes mirror the `MishkaCmsCore.Activity.Behaviours.Source` entry
shape so gervaz columns/filters bind by name and the stream gets a stable `:id`.

Tenant scoping is enforced in the query (Ash won't enforce multitenancy for a
manual-over-Simple read): a master sees every site; a tenant sees its own site
plus global rows of the shared runtime resources (`Query.shares_globals?/1`),
never another tenant. The `:activity_site` column is shown to everyone (a tenant's
rows resolve to its own site or "Global").

# `t`

```elixir
@type t() :: %MishkaCmsCore.Activity.ActivityEntry{
  __lateral_join_source__: term(),
  __meta__: term(),
  __metadata__: term(),
  __order__: term(),
  action: term(),
  actor_label: term(),
  aggregates: term(),
  badge_label: term(),
  badge_tone: term(),
  calculations: term(),
  id: term(),
  inserted_at: term(),
  kind: term(),
  message: term(),
  resource_label: term(),
  severity: term(),
  site_id: term(),
  site_label: term(),
  source_id: term(),
  tone: term(),
  user_id: term()
}
```

# `counts`

```elixir
@spec counts(map() | nil) :: %{
  total: non_neg_integer(),
  info: non_neg_integer(),
  warn: non_neg_integer(),
  error: non_neg_integer()
}
```

The log's KPI totals for `viewer` — `%{total:, info:, warn:, error:}` over the sites and the areas
it may see (`MishkaCmsCore.Activity.Query.counts/1`). All zero for no viewer.

# `default_short_name`

# `from_entry`

Builds an `%ActivityEntry{}` struct from a unified source entry map, deriving
`:kind` (content vs background job). Used by the manual read and the live path.

The day heading the timeline draws is the read's, not the entry's, so `mark_days/2`'s marks ride
along as metadata (see `MishkaCmsCoreWeb.Templates.ActivityTimeline`); a live entry carries none
and simply gets no heading.

# `input`

```elixir
@spec input(values :: map() | Keyword.t()) :: map() | no_return()
```

Validates that the keys in the provided input are valid for at least one action on the resource.

Raises a KeyError error at compile time if not. This exists because generally a struct should only ever
be created by Ash as a result of a successful action. You should not be creating records manually in code,
e.g `%MyResource{value: 1, value: 2}`. Generally that is fine, but often with embedded resources it is nice
to be able to validate the keys that are being provided, e.g

```elixir
Resource
|> Ash.Changeset.for_create(:create, %{embedded: EmbeddedResource.input(foo: 1, bar: 2)})
|> Ash.create()
```

# `input`

```elixir
@spec input(values :: map() | Keyword.t(), action :: atom()) :: map() | no_return()
```

Same as `input/1`, except restricts the keys to values accepted by the action provided.

# `live_visible?`

Whether a live record should appear in a given table view — tenant-visible AND
matching the active filters. Used by the realtime `on_realtime` hook so a live
insert never violates the current Severity/Source filter.

# `primary_key_matches?`

# `total`

```elixir
@spec total(map()) :: non_neg_integer()
```

How many entries the table's current view matches — the total behind "20 of 10,000+ shown", scoped
to the viewer and narrowed by whatever filters are active. Called from the `on_load` hook, which is
where gervaz's filter state lives.

---

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