GraphiteEditor/Graphite · error

Active document is missing from document ids

Error message

Active document is missing from document ids

What it means

document_index does self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids"). PortfolioMessageHandler keeps documents in a HashMap plus an order Vec (document_ids); this expect asserts the two stay in sync. It panics when a caller asks for the index of a document (usually the active one) whose id is absent from the ordering Vec - a state-desync bug where a document was removed from one structure but not the other.

Source

Thrown at editor/src/messages/portfolio/portfolio_message_handler.rs:1929

				compare_storage_against_runtime(&gdd, &legacy_network, byte_store.as_ref(), document_id).await;
			}

			Message::Portfolio(PortfolioMessage::DocumentStorageMounted {
				document_id,
				reopened,
				gdd: Some(gdd),
			})
		};
		future.into()
	}

	/// Returns an iterator over the open documents in order.
	pub fn ordered_document_iterator(&self) -> impl Iterator<Item = &DocumentMessageHandler> {
		self.document_ids.iter().filter_map(|id| self.document(*id))
	}

	fn document_index(&self, document_id: DocumentId) -> usize {
		self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
	}

	pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
		let Some(document_id) = self.active_document_id else {
			return Err("No active document".to_string());
		};
		let Some(active_document) = self.documents.get_mut(&document_id) else {
			return Err("No active document".to_string());
		};

		let result = self.executor.poll_node_graph_evaluation(active_document, document_id, responses);
		if result.is_err() {
			let error = r#"
				<rect x="50%" y="50%" width="460" height="100" transform="translate(-230 -50)" rx="4" fill="var(--color-warning-yellow)" />
				<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="18" fill="var(--color-2-mildblack)">
					<tspan x="50%" dy="-24" font-weight="bold">The document cannot render in its current state.</tspan>
					<tspan x="50%" dy="24">Undo to go back, if available, or check for error details</tspan>
					<tspan x="50%" dy="24">by clicking the <tspan font-style="italic">Node Graph</tspan> button up at the top right.</tspan>

View on GitHub (pinned to c507b35645)

Solutions

  1. Change document_index to return Option<usize> (or use position().unwrap_or default with a logged error) so desync degrades instead of panicking
  2. Audit every mutation of self.documents to also mutate self.document_ids in the same message handler
  3. Add a debug assertion after close/open messages that the map keys and the Vec contents match exactly

Example fix

// before
fn document_index(&self, document_id: DocumentId) -> usize {
	self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
}

// after
fn document_index(&self, document_id: DocumentId) -> Option<usize> {
	self.document_ids.iter().position(|id| id == &document_id)
}
Defensive patterns

Strategy: validation

Validate before calling

if let Some(index) = self.document_ids.iter().position(|id| id == &document_id) {
	// use index
} else {
	log::error!("document {document_id:?} missing from document_ids; desync");
}

Prevention

When it happens

Trigger: A close/remove path deletes from self.documents but forgets self.document_ids (or vice versa), then any operation needing document ordering (tab order, activation after close) calls document_index on the orphaned id.

Common situations: New document-close or session-restore code paths updating only one of the two collections; undo of document open; bugs in reordering logic dropping ids.

Related errors


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/539331ac7f56c182. Report an issue: GitHub.