feat(tahuantinsuyu): "Cartas libres" como sección + guardar a contacto (fase A)
Estructura de cartas no-persistidas con CRUD básico en la UI.
Modelo:
- `FreeChartId(String)` con sentinela `sky_now()` reservado para la
carta del cielo. Otros ids se generan al vuelo como `free-N`.
- `TreeSelection::FreeChart(FreeChartId)` y `FreeChartsRoot`
reemplazan al variante puntual `PresentSky` (que era un caso
especial paralelo).
Tree:
- Sección **"🜨 Cartas libres"** branch fijo al FONDO del tree
(al contrario de "◇ General" que va arriba). Contiene "Cielo
ahora" como primera leaf + cualquier carta libre creada.
Expandida por default.
- Menu contextual:
* sobre la sección: "Nueva carta libre" → `NewFreeChartRequested`
* sobre una carta libre: "Guardar como…" + "Borrar" (`sky-now`
no admite borrar)
- Setter `set_free_charts(Vec<FreeChartEntry>)` actualizado por
el shell tras cada mutación.
Shell:
- Nuevo state: `free_charts: HashMap<FreeChartId, Chart>` +
`next_free_id: u32`.
- `ensure_sky_now` inserta/refresca "Cielo ahora" contra el
reloj actual. Al boot se llama y la carta queda seleccionada.
- `push_free_charts_to_tree` publica la lista al tree
(sky-now primero, después los `free-N` ordenados).
- Handlers de los 3 nuevos eventos:
* `NewFreeChartRequested` → crea entry, selecciona
* `SaveFreeChartRequested(id)` → `save_free_chart_quick`
(MVP: crea contacto nuevo con el label de la carta + carta
bajo él; la fase B reemplaza por modal con dropdown)
* `DeleteFreeChartRequested(id)` → quita de free_charts;
si era la activa, vuelve al Cielo
10 tests verdes (sin cambios — la lógica nueva afecta paths que
no están cubiertos en los smoke tests actuales).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -312,11 +312,35 @@ pub struct ModuleState {
|
||||
// Selección activa (qué muestra el canvas)
|
||||
// =====================================================================
|
||||
|
||||
/// Identificador de una carta "libre" — efímera, no persistida en la
|
||||
/// store. Llave de un `HashMap` en el shell. El valor `SKY_NOW_ID`
|
||||
/// está reservado para la carta del instante actual; otros se
|
||||
/// generan al vuelo como UUIDs string-encoded.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct FreeChartId(pub String);
|
||||
|
||||
impl FreeChartId {
|
||||
pub fn sky_now() -> Self {
|
||||
Self(SKY_NOW_ID.into())
|
||||
}
|
||||
pub fn is_sky_now(&self) -> bool {
|
||||
self.0 == SKY_NOW_ID
|
||||
}
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Sentinela del id de la carta "Cielo ahora" — siempre presente
|
||||
/// como primer elemento de la sección "Cartas libres" del tree.
|
||||
pub const SKY_NOW_ID: &str = "sky-now";
|
||||
|
||||
/// Item activo del tree. El canvas reacciona a este tipo:
|
||||
/// - `Chart` → abre la carta puntual.
|
||||
/// - `Contact` / `Group` → muestra thumbnails de las cartas descendientes.
|
||||
/// - `PresentSky` → carta efímera del instante presente (cielo ahora);
|
||||
/// no vive en la store, se computa al vuelo cuando el host la pide.
|
||||
/// - `FreeChart` → carta libre (no anclada a contacto). Incluye la
|
||||
/// especial "Cielo ahora" + cualquier creada por el usuario.
|
||||
/// - `FreeChartsRoot` → branch virtual de la sección "Cartas libres".
|
||||
/// - `GeneralRoot` → nodo branch virtual que agrupa los contactos
|
||||
/// sin grupo padre (contacts con parent=None).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
@@ -324,7 +348,8 @@ pub enum TreeSelection {
|
||||
Group(GroupId),
|
||||
Contact(ContactId),
|
||||
Chart(ChartId),
|
||||
PresentSky,
|
||||
FreeChart(FreeChartId),
|
||||
FreeChartsRoot,
|
||||
GeneralRoot,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user