Skip to main content

agentmux_srv/backend/wcore/
block.rs

1#![allow(dead_code)]
2// Copyright 2025-2026, AgentMux Corp.
3// SPDX-License-Identifier: Apache-2.0
4
5//! Block CRUD operations.
6
7use uuid::Uuid;
8
9use crate::backend::storage::store::Store;
10use crate::backend::storage::StoreError;
11use crate::backend::obj::*;
12
13/// Create a new block in a tab.
14pub fn create_block(
15    store: &Store,
16    tab_id: &str,
17    meta: MetaMapType,
18) -> Result<Block, StoreError> {
19    let mut tab = store.must_get::<Tab>(tab_id)?;
20
21    let mut block = Block {
22        oid: Uuid::new_v4().to_string(),
23        parentoref: format!("tab:{}", tab_id),
24        meta,
25        ..Default::default()
26    };
27    store.insert(&mut block)?;
28
29    tab.blockids.push(block.oid.clone());
30    store.update(&mut tab)?;
31
32    Ok(block)
33}
34
35/// Delete a block's rows: remove it from its parent tab's `blockids` and
36/// delete the Block row.
37///
38/// SPEC_864 site #6 — the layout-tree prune that used to live here
39/// (wcore-direct `LayoutState` write, the last Path-B writer on the
40/// block-delete path) moved to the reducer: the `delete_block` saga
41/// dispatches `LayoutDeleteNodeByBlock` as its Step 2, and the persist
42/// subscriber writes the reducer's resulting tree from the
43/// `LayoutNodeDeleted{new_tree, tree_cleared}` event. This fn is row-ops
44/// only, keeping `db_layout` single-writer. SPEC_864 Phase 5 deleted the
45/// `prune_block_from_layout`/`heal_layout` backstop that used to live
46/// below — no Path-B writer remains to produce the orphans it swept.
47pub fn delete_block(
48    store: &Store,
49    tab_id: &str,
50    block_id: &str,
51) -> Result<(), StoreError> {
52    let mut tab = store.must_get::<Tab>(tab_id)?;
53    tab.blockids.retain(|id| id != block_id);
54    store.update(&mut tab)?;
55    store.delete::<Block>(block_id)?;
56    // An agent's ownership of a browser pane ends with the pane
57    // (SPEC_AGENT_DRIVEN_BROWSER_PANES_2026_10_07.md §3).
58    crate::server::browser_owner::forget(block_id);
59    // So does anything it was waiting on the user for: an agent mid
60    // hand-off or approval gets Cancelled now, not after the timeout.
61    crate::server::browser_attention::cancel_for(block_id);
62    // And its share of a site limit (its chain's other panes keep theirs).
63    crate::server::browser_allowlist::drop_pane(block_id);
64    // The host drops an Incognito jar once no tab uses it: tell it now.
65    crate::server::browser_owner::changed().notify_one();
66    Ok(())
67}
68
69/// Resolve a block ID from an 8-character prefix within a tab.
70pub fn resolve_block_id_from_prefix(
71    store: &Store,
72    tab_id: &str,
73    prefix: &str,
74) -> Result<String, StoreError> {
75    if prefix.len() != 8 {
76        return Err(StoreError::Other(
77            "block_id prefix must be 8 characters".to_string(),
78        ));
79    }
80    let tab = store.must_get::<Tab>(tab_id)?;
81    for block_id in &tab.blockids {
82        if block_id.starts_with(prefix) {
83            return Ok(block_id.clone());
84        }
85    }
86    Err(StoreError::NotFound)
87}