Los espacios de trabajo de Notez admiten a más de una persona desde hace un tiempo. Las notas tienen propiedades tipadas y filtrables desde hace más. Lo obvio en el hueco entre esas dos funciones es una persona asignada: una nota que dice esta es de Marta.
Es una función pequeña. Un desplegable con las personas del espacio de trabajo, un id en una columna, un chip en la tarjeta. Una tarde de trabajo.
Lo que no es pequeño es la pregunta que nadie se hace hasta que ocurre: Marta se va. ¿Qué pasa con las cuarenta notas que llevan su nombre?
Miembro es una propiedad, no un campo
La primera decisión fue la aburrida, y fue aburrida a propósito.
Podría haber añadido una columna assigneeId a las notas y haberle hecho un selector. En lugar de eso, member se convirtió en el undécimo tipo de propiedad personalizada, junto a texto, número, fecha, selección, relación y los demás.
La diferencia se nota en todas partes menos en el código que lo guarda. Como miembro es una propiedad:
Puedes tener más de una.
ResponsableyRevisorson dos propiedades, no un cambio de esquema.Aparece en el panel de filtros, en el menú de ordenación y en el selector de columnas de la vista de tabla sin que ninguno de ellos tenga que saber qué es una persona.
Se muestra como chip en las tarjetas, en las celdas de la tabla y en la propia nota, por el mismo camino de renderizado que cualquier otra propiedad.
Toda la función es un caso nuevo en unas doce sentencias switch, y cada una se niega a compilar hasta que el caso está cubierto. La lista de comprobación la escribió el compilador.
El precedente ya existía: relation, el tipo que apunta a otras notas. Multivalor, ordenado, con clave foránea hacia lo que referencia, resuelto en el servidor para que el chip y el filtro nunca discrepen sobre lo que significa un valor. Una propiedad de miembro es una propiedad de relación cuyo destino es una persona en vez de una nota. La copié casi línea por línea.
Dónde vive el id
Un valor de miembro es un id de usuario. Había un atajo tentador: los valores de propiedad ya tienen una columna valueText, y un id de usuario es texto. Guardarlo ahí y publicar un día antes.
En lugar de eso le di su propia columna con una clave foránea de verdad, por una razón que no tiene que ver con tener el código ordenado.
Las cuentas se eliminan. Cuando alguien elimina su cuenta de Notez, esa fila desaparece, y todas las referencias a ella tienen que desaparecer con ella. Si no, te queda un valor que apunta a un usuario que ya no existe, y cada lectura tiene que filtrar fantasmas por precaución. Con una clave foránea y on delete cascade, la base de datos se encarga sola, bien y para siempre, con una sola línea de esquema. Con valueText me habría tocado a mí: una limpieza manual en el hook de eliminación de cuentas, más un filtro de ids huérfanos en cada ruta de lectura, más el bug que acabaría publicando el día que alguien añadiera una duodécima ruta de lectura y se olvidara.
La restricción check que dice exactamente una columna de valor no es nula pasó de cinco columnas a seis. Ese es todo el coste.
La pregunta de verdad
Ahora, la interesante. A Marta la quitan del espacio de trabajo, o se va. Su nombre está en cuarenta notas. Hay exactamente tres cosas que puedes hacer:
Bloquear la eliminación. Negarse a quitar a alguien hasta que se borren sus asignaciones. Es la peor opción, y en unos diez segundos se ve por qué: Marta no puede salir de un espacio de trabajo del que quiere irse hasta que un propietario edite a mano cuarenta notas. Un mecanismo de seguridad que toma como rehén a una persona no es un mecanismo de seguridad.
Conservar el valor y marcarlo como muerto. Dejar la fila, resolver el nombre de todos modos y mostrarlo en gris: una lápida (en inglés, tombstone). Es lo que hago con los valores de relación que apuntan a notas en la papelera, así que tenía precedente y era mi primer plan.
Eliminar los valores junto con la membresía. Al quitar a alguien, sus asignaciones se borran en la misma transacción.
Elegí la tercera, y descarté el plan de las lápidas mientras lo ponía por escrito.
Por qué aquí pierden las lápidas
Las lápidas son lo correcto para una nota en la papelera. La nota sigue existiendo, en la papelera, y se puede restaurar durante treinta días. La lápida apunta a algo y te dice la verdad: este destino está en la papelera.
Un miembro que se fue no está en ninguna papelera. No hay nada que restaurar. Y fallan dos cosas concretas:
Nadie puede volver a encontrar esas notas. El filtro de miembro lista a las personas que están ahora en el espacio de trabajo: eso es lo que lo convierte en un selector útil y no en una lista cada vez más larga de todos los que alguna vez pasaron por el espacio de trabajo. Así que Marta no está en él. Cuarenta notas llevan ahora un valor que ningún filtro puede seleccionar. No hay forma de buscarlas; solo puedes toparte con ellas por casualidad.
Las notas se vuelven hostiles a la edición sin avisar. Cada vez que se guarda una propiedad multivalor, se envía el array completo. La validación de escritura comprueba que cada id sea de un miembro actual. Así que una nota asignada a Marta y a alguien que sigue aquí rechazaría el siguiente guardado: tendrías que fijarte en el chip muerto de Marta y quitarlo antes de poder añadir a nadie. Podría hacer una excepción para los ids que ya están guardados, y esa excepción es justo el tipo de regla sutil que se convierte en un bug dos funciones más adelante.
Eliminar tiene un coste real, y conviene decirlo claro: volver a invitar a alguien no le devuelve sus asignaciones. Esa información se pierde de verdad. Creo que compensa, porque la alternativa es información presente pero inalcanzable, que es peor que ausente: parece un dato y se comporta como basura.
El precedente para eliminar resultó estar en el mismo archivo. Eliminar una opción de selección ya borra en cascada sus valores de todas las notas. El diálogo de confirmación para quitar a un miembro ya dice Perderán el acceso a todas las notas de este espacio de trabajo. Borrar la asignación es coherente con las dos cosas.
El caso que se escapa
Hay un cuarto escenario que la transacción no cubre, y vale la pena ser honesto sobre él.
Un miembro al que quitan entre la comprobación de la validación y la inserción (una ventana de milisegundos) deja una fila que la limpieza nunca vio. El usuario sigue existiendo, así que el chip se sigue mostrando. Es un valor obsoleto, y la siguiente edición de esa propiedad se rechaza hasta que lo desmarcas.
Podría cerrarlo con un bloqueo. Decidí no hacerlo: la ventana es de milisegundos, el fallo es un chip confuso y no datos perdidos, y como el selector mantiene fijada arriba la fila seleccionada, siempre puedes desmarcarlo. Algunas condiciones de carrera merecen un comentario en el código, no un bloqueo.
Lo que falta a propósito
Asignar desde el chat de IA. Cuando esto salió, el chat podía leer quién estaba asignado a una nota, pero no asignar a nadie: ninguna herramienta le daba la lista de personas de tu espacio de trabajo, así que solo podía adivinar un id, y una suposición que parece plausible es el peor fallo posible para una escritura. Ahora el chat recibe a todos los miembros del espacio de trabajo con su id y establece una propiedad de miembro como cualquier otro valor.
Tableros agrupados por persona asignada. El tablero está ligado a su propia propiedad de columna. Agrupar por una propiedad cualquiera es un cambio más grande que este.
"Asignadas a mí". Un filtro de un clic con tu propio nombre era lo siguiente obvio, y ya salió: activa Mostrar en la barra lateral en una propiedad de miembro, y la barra lateral lista a todas las personas del espacio de trabajo con el número de notas de cada una. Un clic en tu nombre y ves solo las tuyas.
Notificaciones. Que te asignen una nota no te avisa de nada. Avisa tú a tu compañero.
Cuando escribí esto, dos de esas cuatro eran difíciles y dos simplemente no estaban hechas todavía. Asignar desde el chat y el filtro ya salieron; los tableros agrupados por persona asignada y las notificaciones, no. La diferencia importa, y prefiero decir cuál es cuál a dejar que una lista de funciones dé a entender que todo está a punto de llegar.
Lo que aprendí
Añadir un campo es fácil. Lo que convierte un campo en una función es decidir qué hace los días en que no todo sale bien: cuando la persona se va, cuando se elimina la cuenta, cuando dos escrituras compiten.
Dediqué quizá una quinta parte del tiempo al selector y al chip. La mayor parte se fue en el párrafo del artículo de ayuda que explica qué pasa cuando alguien se va, porque escribir esa frase fue lo que demostró que el primer plan estaba mal.
Sigue leyendo
Compartir notas sin conflictos de fusión — por qué un espacio de trabajo compartido rechaza el segundo de dos guardados en conflicto en lugar de fusionarlos.
Dos asistentes, las mismas herramientas — cómo el chat de IA y el servidor MCP se separaron hasta tener dieciséis y diez herramientas, y el bug del árbol de notas que apareció al igualarlos.
Notez: una app para tomar notas pensada para desarrolladores — qué es Notez, las formas en que entra una nota y qué incluye cada plan.