diff --git a/src/Vervis/Actor/Common.hs b/src/Vervis/Actor/Common.hs
index 242c95f..a02a28c 100644
--- a/src/Vervis/Actor/Common.hs
+++ b/src/Vervis/Actor/Common.hs
@@ -22,7 +22,7 @@ module Vervis.Actor.Common
     , topicAccept
     , topicReject
     , componentInvite
-    , topicRemove
+    , componentRemove
     , topicJoin
     , topicCreateMe
     , componentGrant
@@ -89,6 +89,7 @@ import Vervis.Persist.Discussion
 import Vervis.Recipient (makeRecipientSet, LocalStageBy (..), Aud (..), collectAudience, localActorFollowers, renderLocalActor)
 import Vervis.RemoteActorStore
 import Vervis.Ticket
+import Vervis.Web.Collab
 
 actorFollow
     :: (PersistRecordBackend r SqlBackend, ToBackendKey SqlBackend r)
@@ -1235,228 +1236,485 @@ componentInvite grabKomponent topicComponent now topicKey (Verse authorIdMsig bo
 
         return (action, recipientSet, remoteActors, fwdHosts)
 
-topicRemove
-    :: (PersistRecordBackend topic SqlBackend, ToBackendKey SqlBackend topic)
-    => (topic -> ResourceId)
-    -> (forall f. f topic -> LocalResourceBy f)
+-- Meaning: An actor A is removing actor B from a collection C
+-- Behavior:
+--  * If C is my collaborators collection
+--      * Verify A isn't removing themselves
+--      * Verify A is authorized by me to remove actors from me
+--      * Verify B already has a Grant for me
+--      * Remove the whole Collab record from DB
+--      * Forward the Remove to my followers
+--      * Send a Revoke:
+--          * To: Actor B
+--          * CC: Actor A, B's followers, my followers
+--
+--  * If C is my projects collection
+--      * Verify A's request is authorized
+--      * Verify B is an enabled project of mine
+--      * Remove the whole Stem record from DB
+--      * Forward to followers
+--      * Publish a Revoke on the start-Grant I'd sent to B
+--          * To: Actor B
+--          * CC: Actor A, B's followers, my followers
+--
+--  * If I'm B, and C is some project's components collection
+--      * Just forward to my followers
+componentRemove
+    :: forall topic.
+       (PersistRecordBackend topic SqlBackend, ToBackendKey SqlBackend topic)
+    => (topic -> KomponentId)
+    -> (forall f. f topic -> ComponentBy f)
     -> UTCTime
     -> Key topic
     -> Verse
     -> AP.Remove URIMode
     -> ActE (Text, Act (), Next)
-topicRemove grabResource topicResource now topicKey (Verse authorIdMsig body) remove = do
+componentRemove grabKomponent topicComponent now topicKey (Verse authorIdMsig body) remove = do
 
-    -- Check capability
-    capability <- do
+    let author = bimap (view _1) (remoteAuthorURI . view _1) authorIdMsig
+    (collection, item) <- parseRemove author remove
+    case (collection, item) of
+        (Left (Left lr), _)
+            | lr == topicResource topicKey ->
+                removeCollab item
+        (Left (Right target), _)
+            | addTargetComponentProjects target == Just (topicComponent topicKey) ->
+                removeProjectActive item
+        (_, Left la) | la == resourceToActor (topicResource topicKey) ->
+            case collection of
+                Left (Right (ATProjectComponents j)) ->
+                    removeProjectPassive $ Left j
+                Right (ObjURI h luColl) -> do
+                    -- NOTE this is HTTP GET done synchronously in the activity
+                    -- handler
+                    manager <- asksEnv envHttpManager
+                    c <- AP.fetchAPID_T manager (AP.collectionId :: AP.Collection FedURI URIMode -> LocalURI) h luColl
+                    lu <- fromMaybeE (AP.collectionContext c) "No context"
+                    rwc <- AP.fetchRWC_T manager h lu
+                    AP.Actor l d <-
+                        case AP.rwcResource rwc of
+                            AP.ResourceActor a -> pure a
+                            AP.ResourceChild _ _ -> throwE "Remove.target remote ResourceChild"
+                    let typ = AP.actorType d
+                    if typ == AP.ActorTypeProject && Just luColl == AP.rwcComponents rwc
+                        then removeProjectPassive $ Right $ ObjURI h lu
+                        else throwE "Weird collection situation"
+                _ -> throwE "I'm being removed from somewhere irrelevant"
+        _ -> throwE "This Remove isn't for me"
 
-        -- Verify that a capability is provided
+    where
+
+    topicResource :: forall f. f topic -> LocalResourceBy f
+    topicResource = componentResource . topicComponent
+
+    removeCollab item = do
+
+        memberByKey <-
+            bitraverse
+                (\case
+                    LocalActorPerson p -> pure p
+                    _ -> throwE "Not accepting non-person actors as collabs"
+                )
+                pure
+                item
+
+        -- Check capability
         uCap <- do
             let muCap = AP.activityCapability $ actbActivity body
             fromMaybeE muCap "No capability provided"
 
-        -- Verify the capability URI is one of:
-        --   * Outbox item URI of a local actor, i.e. a local activity
-        --   * A remote URI
-        cap <- nameExceptT "Remove.capability" $ parseActivityURI' uCap
+        -- Verify the specified capability gives relevant access
+        verifyCapability'' uCap authorIdMsig (topicResource topicKey) AP.RoleAdmin
 
-        -- Verify the capability is local
-        case cap of
-            Left (actorByKey, _, outboxItemID) ->
-                return (actorByKey, outboxItemID)
-            _ -> throwE "Capability is remote i.e. definitely not by me"
+        maybeNew <- withDBExcept $ do
 
-    -- Check remove
-    memberByKey <- do
-        let author = bimap (view _1) (remoteAuthorURI . view _1) authorIdMsig
-        (resource, memberOrComp) <- parseRemove author remove
-        unless (Left (Left $ topicResource topicKey) == resource) $
-            throwE "Remove topic isn't my collabs URI"
-        bitraverse
-            (\case
-                LocalActorPerson p -> pure p
-                _ -> throwE "Not accepting non-person actors as collabs"
-            )
-            pure
-            memberOrComp
+            -- Find member in our DB
+            memberDB <-
+                bitraverse
+                    (flip getEntityE "Member not found in DB")
+                    (\ u@(ObjURI h lu) -> (,u) <$> do
+                        maybeActor <- lift $ runMaybeT $ do
+                            iid <- MaybeT $ getKeyBy $ UniqueInstance h
+                            roid <- MaybeT $ getKeyBy $ UniqueRemoteObject iid lu
+                            MaybeT $ getBy $ UniqueRemoteActor roid
+                        fromMaybeE maybeActor "Remote removee not found in DB"
+                    )
+                    memberByKey
 
-    maybeNew <- withDBExcept $ do
+            -- Grab me from DB
+            komponentID <- lift $ grabKomponent <$> getJust topicKey
+            Komponent resourceID <- lift $ getJust komponentID
+            Resource topicActorID <- lift $ getJust resourceID
+            topicActor <- lift $ getJust topicActorID
 
-        -- Find member in our DB
-        memberDB <-
+            -- Find the collab that the member already has for me
+            existingCollabIDs <-
+                lift $ case memberDB of
+                    Left (Entity personID _) ->
+                        fmap (map $ over _1 Left) $
+                        E.select $ E.from $ \ (collab `E.InnerJoin` recipl) -> do
+                            E.on $
+                                collab E.^. CollabId E.==.
+                                recipl E.^. CollabRecipLocalCollab
+                            E.where_ $
+                                collab E.^. CollabTopic E.==. E.val resourceID E.&&.
+                                recipl E.^. CollabRecipLocalPerson E.==. E.val personID
+                            return
+                                ( recipl E.^. persistIdField
+                                , recipl E.^. CollabRecipLocalCollab
+                                )
+                    Right (Entity remoteActorID _, _) ->
+                        fmap (map $ over _1 Right) $
+                        E.select $ E.from $ \ (collab `E.InnerJoin` recipr) -> do
+                            E.on $
+                                collab E.^. CollabId E.==.
+                                recipr E.^. CollabRecipRemoteCollab
+                            E.where_ $
+                                collab E.^. CollabTopic E.==. E.val resourceID E.&&.
+                                recipr E.^. CollabRecipRemoteActor E.==. E.val remoteActorID
+                            return
+                                ( recipr E.^. persistIdField
+                                , recipr E.^. CollabRecipRemoteCollab
+                                )
+            (recipID, E.Value collabID) <-
+                case existingCollabIDs of
+                    [] -> throwE "Remove object isn't a member of me"
+                    [collab] -> return collab
+                    _ -> error "Multiple collabs found for removee"
+
+            -- Verify the Collab is enabled
+            maybeEnabled <- lift $ getBy $ UniqueCollabEnable collabID
+            Entity enableID (CollabEnable _ grantID) <-
+                fromMaybeE maybeEnabled "Remove object isn't a member of me yet"
+
+            -- Verify that at least 1 more enabled Admin collab for me exists
+            otherCollabIDs <-
+                lift $ E.select $ E.from $ \ (collab `E.InnerJoin` enable) -> do
+                    E.on $
+                        collab E.^. CollabId E.==.
+                        enable E.^. CollabEnableCollab
+                    E.where_ $
+                        collab E.^. CollabTopic E.==. E.val resourceID E.&&.
+                        collab E.^. CollabId E.!=. E.val collabID
+                    return $ collab E.^. CollabId
+            when (null otherCollabIDs) $
+                throwE "No other admins exist, can't remove"
+
+            maybeRemoveDB <- lift $ insertToInbox now authorIdMsig body (actorInbox topicActor) False
+            lift $ for maybeRemoveDB $ \ (inboxItemID, _removeDB) -> do
+
+                -- Delete the whole Collab record
+                deleteBy $ UniqueCollabDelegLocal enableID
+                deleteBy $ UniqueCollabDelegRemote enableID
+                delete enableID
+                case recipID of
+                    Left (E.Value l) -> do
+                        deleteBy $ UniqueCollabRecipLocalJoinCollab l
+                        deleteBy $ UniqueCollabRecipLocalAcceptCollab l
+                        delete l
+                    Right (E.Value r) -> do
+                        deleteBy $ UniqueCollabRecipRemoteJoinCollab r
+                        deleteBy $ UniqueCollabRecipRemoteAcceptCollab r
+                        delete r
+                fulfills <- do
+                    mf <- runMaybeT $ asum
+                        [ Left <$> MaybeT (getKeyBy $ UniqueCollabFulfillsLocalTopicCreation collabID)
+                        , Right . Left <$> MaybeT (getKeyBy $ UniqueCollabFulfillsInvite collabID)
+                        , Right . Right <$> MaybeT (getKeyBy $ UniqueCollabFulfillsJoin collabID)
+                        ]
+                    maybe (error $ "No fulfills for collabID#" ++ show collabID) pure mf
+                case fulfills of
+                    Left fc -> delete fc
+                    Right (Left fi) -> do
+                        deleteBy $ UniqueCollabInviterLocal fi
+                        deleteBy $ UniqueCollabInviterRemote fi
+                        delete fi
+                    Right (Right fj) -> do
+                        deleteBy $ UniqueCollabApproverLocal fj
+                        deleteBy $ UniqueCollabApproverRemote fj
+                        delete fj
+                delete collabID
+
+                -- Prepare forwarding Remove to my followers
+                sieve <- lift $ do
+                    topicHash <- encodeKeyHashid topicKey
+                    let topicByHash = resourceToActor $ topicResource topicHash
+                    return $ makeRecipientSet [] [localActorFollowers topicByHash]
+
+                -- Prepare a Revoke activity and insert to my outbox
+                revoke@(actionRevoke, _, _, _) <-
+                    lift $ prepareRevoke memberDB grantID
+                let recipByKey = resourceToActor $ topicResource topicKey
+                revokeID <- insertEmptyOutboxItem' (actorOutbox topicActor) now
+                _luRevoke <- updateOutboxItem' recipByKey revokeID actionRevoke
+
+                return (topicActorID, sieve, revokeID, revoke, inboxItemID)
+
+        case maybeNew of
+            Nothing -> done "I already have this activity in my inbox"
+            Just (topicActorID, sieve, revokeID, (actionRevoke, localRecipsRevoke, remoteRecipsRevoke, fwdHostsRevoke), inboxItemID) -> do
+                let topicByID = resourceToActor $ topicResource topicKey
+                forwardActivity authorIdMsig body topicByID topicActorID sieve
+                lift $ sendActivity
+                    topicByID topicActorID localRecipsRevoke
+                    remoteRecipsRevoke fwdHostsRevoke revokeID actionRevoke
+                doneDB inboxItemID "[Collab] Deleted the Grant/Collab, forwarded Remove, sent Revoke"
+
+        where
+
+        prepareRevoke member grantID = do
+            encodeRouteHome <- getEncodeRouteHome
+            encodeRouteLocal <- getEncodeRouteLocal
+
+            recipHash <- encodeKeyHashid topicKey
+            let topicByHash = resourceToActor $ topicResource recipHash
+
+            memberHash <- bitraverse (encodeKeyHashid . entityKey) pure member
+
+            audRemover <- makeAudSenderOnly authorIdMsig
+            let audience =
+                    let audMember =
+                            case memberHash of
+                                Left p ->
+                                    AudLocal [LocalActorPerson p] [LocalStagePersonFollowers p]
+                                Right (Entity _ actor, ObjURI h lu) ->
+                                    AudRemote h [lu] (maybeToList $ remoteActorFollowers actor)
+                        audTopic = AudLocal [] [localActorFollowers topicByHash]
+                    in  [audRemover, audMember, audTopic]
+
+                (recipientSet, remoteActors, fwdHosts, audLocal, audRemote) =
+                    collectAudience audience
+
+                recips = map encodeRouteHome audLocal ++ audRemote
+            uRemove <- getActivityURI authorIdMsig
+            luGrant <- do
+                grantHash <- encodeKeyHashid grantID
+                return $ encodeRouteLocal $ activityRoute topicByHash grantHash
+            let action = AP.Action
+                    { AP.actionCapability = Nothing
+                    , AP.actionSummary    = Nothing
+                    , AP.actionAudience   = AP.Audience recips [] [] [] [] []
+                    , AP.actionFulfills   = [uRemove]
+                    , AP.actionSpecific   = AP.RevokeActivity AP.Revoke
+                        { AP.revokeObject = luGrant :| []
+                        }
+                    }
+
+            return (action, recipientSet, remoteActors, fwdHosts)
+
+    removeProjectActive item = do
+
+        project <-
             bitraverse
-                (flip getEntityE "Member not found in DB")
-                (\ u@(ObjURI h lu) -> (,u) <$> do
-                    maybeActor <- lift $ runMaybeT $ do
-                        iid <- MaybeT $ getKeyBy $ UniqueInstance h
-                        roid <- MaybeT $ getKeyBy $ UniqueRemoteObject iid lu
-                        MaybeT $ getBy $ UniqueRemoteActor roid
-                    fromMaybeE maybeActor "Remote removee not found in DB"
+                (\case
+                    LocalActorProject j -> pure j
+                    _ -> throwE "Local object isn't a Project"
                 )
-                memberByKey
+                pure
+                item
 
-        -- Grab me from DB
-        resourceID <- lift $ grabResource <$> getJust topicKey
-        Resource topicActorID <- lift $ getJust resourceID
-        topicActor <- lift $ getJust topicActorID
+        -- Check capability
+        uCap <- do
+            let muCap = AP.activityCapability $ actbActivity body
+            fromMaybeE muCap "No capability provided"
 
         -- Verify the specified capability gives relevant access
-        verifyCapability'
-            capability authorIdMsig (topicResource topicKey) AP.RoleAdmin
+        verifyCapability'' uCap authorIdMsig (topicResource topicKey) AP.RoleAdmin
 
-        -- Find the collab that the member already has for me
-        existingCollabIDs <-
-            lift $ case memberDB of
-                Left (Entity personID _) ->
-                    fmap (map $ over _1 Left) $
-                    E.select $ E.from $ \ (collab `E.InnerJoin` recipl) -> do
-                        E.on $
-                            collab E.^. CollabId E.==.
-                            recipl E.^. CollabRecipLocalCollab
-                        E.where_ $
-                            collab E.^. CollabTopic E.==. E.val resourceID E.&&.
-                            recipl E.^. CollabRecipLocalPerson E.==. E.val personID
-                        return
-                            ( recipl E.^. persistIdField
-                            , recipl E.^. CollabRecipLocalCollab
-                            )
-                Right (Entity remoteActorID _, _) ->
-                    fmap (map $ over _1 Right) $
-                    E.select $ E.from $ \ (collab `E.InnerJoin` recipr) -> do
-                        E.on $
-                            collab E.^. CollabId E.==.
-                            recipr E.^. CollabRecipRemoteCollab
-                        E.where_ $
-                            collab E.^. CollabTopic E.==. E.val resourceID E.&&.
-                            recipr E.^. CollabRecipRemoteActor E.==. E.val remoteActorID
-                        return
-                            ( recipr E.^. persistIdField
-                            , recipr E.^. CollabRecipRemoteCollab
-                            )
-        (recipID, E.Value collabID) <-
-            case existingCollabIDs of
-                [] -> throwE "Remove object isn't a member of me"
-                [collab] -> return collab
-                _ -> error "Multiple collabs found for removee"
+        maybeNew <- withDBExcept $ do
 
-        -- Verify the Collab is enabled
-        maybeEnabled <- lift $ getBy $ UniqueCollabEnable collabID
-        Entity enableID (CollabEnable _ grantID) <-
-            fromMaybeE maybeEnabled "Remove object isn't a member of me yet"
+            -- Find project in our DB
+            projectDB <-
+                bitraverse
+                    (flip getEntityE "Member not found in DB")
+                    (\ u@(ObjURI h lu) -> (,u) <$> do
+                        maybeActor <- lift $ runMaybeT $ do
+                            iid <- MaybeT $ getKeyBy $ UniqueInstance h
+                            roid <- MaybeT $ getKeyBy $ UniqueRemoteObject iid lu
+                            MaybeT $ getBy $ UniqueRemoteActor roid
+                        fromMaybeE maybeActor "Remote removee not found in DB"
+                    )
+                    project
 
-        -- Verify that at least 1 more enabled Admin collab for me exists
-        otherCollabIDs <-
-           lift $  E.select $ E.from $ \ (collab `E.InnerJoin` enable) -> do
-                E.on $
-                    collab E.^. CollabId E.==.
-                    enable E.^. CollabEnableCollab
-                E.where_ $
-                    collab E.^. CollabTopic E.==. E.val resourceID E.&&.
-                    collab E.^. CollabId E.!=. E.val collabID
-                return $ collab E.^. CollabId
-        when (null otherCollabIDs) $
-            throwE "No other admins exist, can't remove"
+            -- Grab me from DB
+            komponentID <- lift $ grabKomponent <$> getJust topicKey
+            Komponent resourceID <- lift $ getJust komponentID
+            Resource topicActorID <- lift $ getJust resourceID
+            topicActor <- lift $ getJust topicActorID
 
-        maybeRemoveDB <- lift $ insertToInbox now authorIdMsig body (actorInbox topicActor) False
-        lift $ for maybeRemoveDB $ \ (inboxItemID, _removeDB) -> do
+            -- Find my Stem record for this project
+            existingStemIDs <-
+                lift $ case projectDB of
+                    Left (Entity projectID _) ->
+                        fmap (map $ over _1 Left) $
+                        E.select $ E.from $ \ (stem `E.InnerJoin` project) -> do
+                            E.on $ stem E.^. StemId E.==. project E.^. StemProjectLocalStem
+                            E.where_ $
+                                project E.^. StemProjectLocalProject E.==. E.val projectID E.&&.
+                                stem E.^. StemHolder E.==. E.val komponentID
+                            return
+                                ( project E.^. StemProjectLocalId
+                                , project E.^. StemProjectLocalStem
+                                )
+                    Right (Entity remoteActorID _, _) ->
+                        fmap (map $ over _1 Right) $
+                        E.select $ E.from $ \ (stem `E.InnerJoin` project) -> do
+                            E.on $ stem E.^. StemId E.==. project E.^. StemProjectRemoteStem
+                            E.where_ $
+                                project E.^. StemProjectRemoteProject E.==. E.val remoteActorID E.&&.
+                                stem E.^. StemHolder E.==. E.val komponentID
+                            return
+                                ( project E.^. StemProjectRemoteId
+                                , project E.^. StemProjectRemoteStem
+                                )
+            (recipID, E.Value stemID) <-
+                case existingStemIDs of
+                    [] -> throwE "Remove object isn't a project of mine"
+                    [stem] -> return stem
+                    _ -> error "Multiple Stems found for removee"
 
-            -- Delete the whole Collab record
-            deleteBy $ UniqueCollabDelegLocal enableID
-            deleteBy $ UniqueCollabDelegRemote enableID
-            delete enableID
-            case recipID of
-                Left (E.Value l) -> do
-                    deleteBy $ UniqueCollabRecipLocalJoinCollab l
-                    deleteBy $ UniqueCollabRecipLocalAcceptCollab l
-                    delete l
-                Right (E.Value r) -> do
-                    deleteBy $ UniqueCollabRecipRemoteJoinCollab r
-                    deleteBy $ UniqueCollabRecipRemoteAcceptCollab r
-                    delete r
-            fulfills <- do
-                mf <- runMaybeT $ asum
-                    [ Left <$> MaybeT (getKeyBy $ UniqueCollabFulfillsLocalTopicCreation collabID)
-                    , Right . Left <$> MaybeT (getKeyBy $ UniqueCollabFulfillsInvite collabID)
-                    , Right . Right <$> MaybeT (getKeyBy $ UniqueCollabFulfillsJoin collabID)
-                    ]
-                maybe (error $ "No fulfills for collabID#" ++ show collabID) pure mf
-            case fulfills of
-                Left fc -> delete fc
-                Right (Left fi) -> do
-                    deleteBy $ UniqueCollabInviterLocal fi
-                    deleteBy $ UniqueCollabInviterRemote fi
-                    delete fi
-                Right (Right fj) -> do
-                    deleteBy $ UniqueCollabApproverLocal fj
-                    deleteBy $ UniqueCollabApproverRemote fj
-                    delete fj
-            delete collabID
+            -- Verify the Stem is enabled
+            maybeDelegatorGrant <-
+                lift $
+                case recipID of
+                    Left (E.Value localID) -> fmap Left <$> getBy (UniqueStemProjectGrantLocalProject localID)
+                    Right (E.Value remoteID) -> fmap Right <$> getBy (UniqueStemProjectGrantRemoteProject remoteID)
+            delegatorGrant <- fromMaybeE maybeDelegatorGrant "Stem not enabled yet"
 
-            -- Prepare forwarding Remove to my followers
-            sieve <- lift $ do
-                topicHash <- encodeKeyHashid topicKey
-                let topicByHash = resourceToActor $ topicResource topicHash
-                return $ makeRecipientSet [] [localActorFollowers topicByHash]
+            -- Grab start-Grant that I'm going to revoke
+            let componentAcceptID =
+                    case delegatorGrant of
+                        Left (Entity _ (StemProjectGrantLocal ca _ _)) -> ca
+                        Right (Entity _ (StemProjectGrantRemote ca _ _)) -> ca
+            Entity startID (StemDelegateLocal _ grantID) <- do
+                maybeStart <-
+                    lift $ getBy $ UniqueStemDelegateLocal componentAcceptID
+                fromMaybeE maybeStart "Start-Grant not sent yet"
 
-            -- Prepare a Revoke activity and insert to my outbox
-            revoke@(actionRevoke, _, _, _) <-
-                lift $ prepareRevoke memberDB grantID
-            let recipByKey = resourceToActor $ topicResource topicKey
-            revokeID <- insertEmptyOutboxItem' (actorOutbox topicActor) now
-            _luRevoke <- updateOutboxItem' recipByKey revokeID actionRevoke
+            maybeRemoveDB <- lift $ insertToInbox now authorIdMsig body (actorInbox topicActor) False
+            lift $ for maybeRemoveDB $ \ (inboxItemID, _removeDB) -> do
 
-            return (topicActorID, sieve, revokeID, revoke, inboxItemID)
+                -- Delete the whole Stem record
+                usOrThem <-
+                    requireEitherAlt
+                        (getKeyBy $ UniqueStemOriginAdd stemID)
+                        (getKeyBy $ UniqueStemOriginInvite stemID)
+                        "Neither us nor them"
+                        "Both us and them"
 
-    case maybeNew of
-        Nothing -> done "I already have this activity in my inbox"
-        Just (topicActorID, sieve, revokeID, (actionRevoke, localRecipsRevoke, remoteRecipsRevoke, fwdHostsRevoke), inboxItemID) -> do
-            let topicByID = resourceToActor $ topicResource topicKey
-            forwardActivity authorIdMsig body topicByID topicActorID sieve
-            lift $ sendActivity
-                topicByID topicActorID localRecipsRevoke
-                remoteRecipsRevoke fwdHostsRevoke revokeID actionRevoke
-            doneDB inboxItemID "Deleted the Grant/Collab, forwarded Remove, sent Revoke"
+                delete startID
+                case delegatorGrant of
+                    Left (Entity localID _) -> delete localID
+                    Right (Entity remoteID _) -> delete remoteID
+                case usOrThem of
+                    Left usID -> delete usID
+                    Right themID -> do
+                        deleteBy $ UniqueStemProjectAcceptLocal themID
+                        deleteBy $ UniqueStemProjectGestureRemote themID
+                        deleteBy $ UniqueStemProjectGestureLocal themID
+                        delete themID
+                delete componentAcceptID
+                deleteBy $ UniqueStemComponentGestureLocal stemID
+                deleteBy $ UniqueStemComponentGestureRemote stemID
+                case recipID of
+                    Left (E.Value localID) -> delete localID
+                    Right (E.Value remoteID) -> delete remoteID
+                delete stemID
 
-    where
+                -- Prepare forwarding Remove to my followers
+                sieve <- lift $ do
+                    topicHash <- encodeKeyHashid topicKey
+                    let topicByHash = resourceToActor $ topicResource topicHash
+                    return $ makeRecipientSet [] [localActorFollowers topicByHash]
 
-    prepareRevoke member grantID = do
-        encodeRouteHome <- getEncodeRouteHome
-        encodeRouteLocal <- getEncodeRouteLocal
+                -- Prepare a Revoke activity and insert to my outbox
+                revoke@(actionRevoke, _, _, _) <-
+                    lift $ prepareRevoke projectDB grantID
+                let recipByKey = resourceToActor $ topicResource topicKey
+                revokeID <- insertEmptyOutboxItem' (actorOutbox topicActor) now
+                _luRevoke <- updateOutboxItem' recipByKey revokeID actionRevoke
 
-        recipHash <- encodeKeyHashid topicKey
-        let topicByHash = resourceToActor $ topicResource recipHash
+                return (topicActorID, sieve, revokeID, revoke, inboxItemID)
 
-        memberHash <- bitraverse (encodeKeyHashid . entityKey) pure member
+        case maybeNew of
+            Nothing -> done "I already have this activity in my inbox"
+            Just (topicActorID, sieve, revokeID, (actionRevoke, localRecipsRevoke, remoteRecipsRevoke, fwdHostsRevoke), inboxItemID) -> do
+                let topicByID = resourceToActor $ topicResource topicKey
+                forwardActivity authorIdMsig body topicByID topicActorID sieve
+                lift $ sendActivity
+                    topicByID topicActorID localRecipsRevoke
+                    remoteRecipsRevoke fwdHostsRevoke revokeID actionRevoke
+                doneDB inboxItemID "[Project-active] Deleted the Stem, forwarded Remove, sent Revoke"
 
-        audRemover <- makeAudSenderOnly authorIdMsig
-        let audience =
-                let audMember =
-                        case memberHash of
-                            Left p ->
-                                AudLocal [LocalActorPerson p] [LocalStagePersonFollowers p]
-                            Right (Entity _ actor, ObjURI h lu) ->
-                                AudRemote h [lu] (maybeToList $ remoteActorFollowers actor)
-                    audTopic = AudLocal [] [localActorFollowers topicByHash]
-                in  [audRemover, audMember, audTopic]
+        where
 
-            (recipientSet, remoteActors, fwdHosts, audLocal, audRemote) =
-                collectAudience audience
+        prepareRevoke project grantID = do
+            encodeRouteHome <- getEncodeRouteHome
+            encodeRouteLocal <- getEncodeRouteLocal
 
-            recips = map encodeRouteHome audLocal ++ audRemote
-        uRemove <- getActivityURI authorIdMsig
-        luGrant <- do
-            grantHash <- encodeKeyHashid grantID
-            return $ encodeRouteLocal $ activityRoute topicByHash grantHash
-        let action = AP.Action
-                { AP.actionCapability = Nothing
-                , AP.actionSummary    = Nothing
-                , AP.actionAudience   = AP.Audience recips [] [] [] [] []
-                , AP.actionFulfills   = [uRemove]
-                , AP.actionSpecific   = AP.RevokeActivity AP.Revoke
-                    { AP.revokeObject = luGrant :| []
+            recipHash <- encodeKeyHashid topicKey
+            let topicByHash = resourceToActor $ topicResource recipHash
+
+            projectHash <- bitraverse (encodeKeyHashid . entityKey) pure project
+
+            audRemover <- makeAudSenderOnly authorIdMsig
+            let audience =
+                    let audProject =
+                            case projectHash of
+                                Left j ->
+                                    AudLocal [LocalActorProject j] [LocalStageProjectFollowers j]
+                                Right (Entity _ actor, ObjURI h lu) ->
+                                    AudRemote h [lu] (maybeToList $ remoteActorFollowers actor)
+                        audMe = AudLocal [] [localActorFollowers topicByHash]
+                    in  [audRemover, audProject, audMe]
+
+                (recipientSet, remoteActors, fwdHosts, audLocal, audRemote) =
+                    collectAudience audience
+
+                recips = map encodeRouteHome audLocal ++ audRemote
+            uRemove <- getActivityURI authorIdMsig
+            luGrant <- do
+                grantHash <- encodeKeyHashid grantID
+                return $ encodeRouteLocal $ activityRoute topicByHash grantHash
+            let action = AP.Action
+                    { AP.actionCapability = Nothing
+                    , AP.actionSummary    = Nothing
+                    , AP.actionAudience   = AP.Audience recips [] [] [] [] []
+                    , AP.actionFulfills   = [uRemove]
+                    , AP.actionSpecific   = AP.RevokeActivity AP.Revoke
+                        { AP.revokeObject = luGrant :| []
+                        }
                     }
-                }
 
-        return (action, recipientSet, remoteActors, fwdHosts)
+            return (action, recipientSet, remoteActors, fwdHosts)
+
+    removeProjectPassive _project = do
+
+        maybeNew <- withDBExcept $ do
+
+            -- Grab me from DB
+            komponentID <- lift $ grabKomponent <$> getJust topicKey
+            Komponent resourceID <- lift $ getJust komponentID
+            Resource topicActorID <- lift $ getJust resourceID
+            topicActor <- lift $ getJust topicActorID
+
+            maybeRemoveDB <- lift $ insertToInbox now authorIdMsig body (actorInbox topicActor) False
+            lift $ for maybeRemoveDB $ \ (inboxItemID, _removeDB) -> do
+
+                -- Prepare forwarding Remove to my followers
+                sieve <- lift $ do
+                    topicHash <- encodeKeyHashid topicKey
+                    let topicByHash = resourceToActor $ topicResource topicHash
+                    return $ makeRecipientSet [] [localActorFollowers topicByHash]
+
+                return (topicActorID, sieve, inboxItemID)
+
+        case maybeNew of
+            Nothing -> done "I already have this activity in my inbox"
+            Just (topicActorID, sieve, inboxItemID) -> do
+                let topicByID = resourceToActor $ topicResource topicKey
+                forwardActivity authorIdMsig body topicByID topicActorID sieve
+                doneDB inboxItemID "[Project-passive] Just forwarded Remove"
 
 topicJoin
     :: (PersistRecordBackend topic SqlBackend, ToBackendKey SqlBackend topic)
diff --git a/src/Vervis/Actor/Deck.hs b/src/Vervis/Actor/Deck.hs
index 66d3707..0283ecc 100644
--- a/src/Vervis/Actor/Deck.hs
+++ b/src/Vervis/Actor/Deck.hs
@@ -627,24 +627,13 @@ deckInvite
     -> ActE (Text, Act (), Next)
 deckInvite = componentInvite deckKomponent ComponentDeck
 
--- Meaning: An actor A is removing actor B from a resource
--- Behavior:
---      * Verify the resource is me
---      * Verify A isn't removing themselves
---      * Verify A is authorized by me to remove actors from me
---      * Verify B already has a Grant for me
---      * Remove the whole Collab record from DB
---      * Forward the Remove to my followers
---      * Send a Revoke:
---          * To: Actor B
---          * CC: Actor A, B's followers, my followers
 deckRemove
     :: UTCTime
     -> DeckId
     -> Verse
     -> AP.Remove URIMode
     -> ActE (Text, Act (), Next)
-deckRemove = topicRemove deckResource LocalResourceDeck
+deckRemove = componentRemove deckKomponent ComponentDeck
 
 -- Meaning: An actor A asked to join a resource
 -- Behavior: