diff --git a/.adr-dir b/.adr-dir
new file mode 100644
index 0000000..0d38988
--- /dev/null
+++ b/.adr-dir
@@ -0,0 +1 @@
+doc/architecture/decisions
diff --git a/architecture/gfx_store.md b/architecture/gfx_store.md
deleted file mode 100644
index 4443c20..0000000
--- a/architecture/gfx_store.md
+++ /dev/null
@@ -1,289 +0,0 @@
-# Background and Rationale
-
-`gromit` currently follows a "draw-and-forget" strategy, i.e. each
-drawing operation, once completed, is drawn on a `cairo_surface_t`
-(`GromitData.back_buffer`).
-
-While this is very straightforward, it prevents the implementation of
-
-- dynamic content, e.g. items that fade after a certain time and
- disappear, or otherwise change through time
-
-- editing of past content (e.g. select and move or delete an old item)
-
-Currently, a work-around exists in the form of an `aux_backbuffer`,
-which stores the state prior to the drawing operation in
-progress. This allows dynamic content during the ongoing drawing
-operation, and is used in e.g. `LINE`, `SMOOTH`, `RECT`.
-However, the drawing is split up over various callbacks (`on_press`,
-`on_motion`, and `on_release`, which complicates maintenance).
-
-Here, I propose to implement a "graphics object store" that contains
-the elements that have been produced so far. By re-executing these
-drawing operations (function `redraw`), the concent of `backbuffer`
-could be re-created at any time.
-
-# Implementation
-
-A graphics object store is added to `GromitData`. This object store
-simple is a `GList` of `GfxObject`. Each `GfxObject` has the
-following fields:
-
- typedef struct {
- guint id; // unique id
- GfxType type; // the type of object, e.g. Stroke, or Text
- gboolean dynamic; // TRUE = content is dynamically updated
- gboolean selected; // TRUE = object is currently selected
- guint32 capabilities; // bit mask with capabilities (see below)
- BoundingBox extent; // the objects rectangular extent
- bool (*do)(GfxObject *, action, void *); // object-specific methods
- // ------------- object-specific fields -----------
- ...
- } GfxObject;
-
-The object specific fields allow for an inheritance-like structure,
-i.e. each specialized object can be downcast to the above `GfxObject`.
-
-For a normal stroked path, the specific field could be:
-
- typedef struct {
- .... common fields ....
- // ------------- object-specific fields -----------
- GList *coordinates;
- GdkRGBA color;
- gfloat arrowsize;
- ArrowType arrowtype;
- } GfxObjectPath;
-
-For a text object, one could have:
-
- typedef struct {
- .... common fields ....
- // ------------- object-specific fields -----------
- GdkRGBA color;
- gchar *text;
- gfloat fontsize;
- gfloat x, y;
- } GfxObjectPath;
-
-A path that fades away after a certain time could look like so:
-
- typedef struct {
- .... common fields ....
- // ------------- object-specific fields -----------
- GList *coordinates;
- GdkRGBA color;
- gfloat arrowsize;
- ArrowTyype arrowtype;
- guint64 start_fading; // timestamps in
- guint64 end_fading; // microseconds
- } GfxObjectFadingPath;
-
-## Capabilities
-
-Each `gfx_object` has a bit mask indicating its "capabilities". These
-capabilities imply that certain actions are implemented in the `do`
-function.
-
-
-
- Capability | Description |
-
-
- draw | object can be drawn |
-
-
- draw_transformed | object can be drawn in transformed state;
- this allows for dynamic representation during
- operations such as dragging |
-
-
- move | object can be moved |
-
-
- isotropic_scale | object can be scaled isotropically |
-
-
- anisotropic_scale | object can be scaled anisotropically,
- i.e. differently in X and Y direction
- isotropic_scale also must be set |
-
-
- rotate | object can be rotated |
-
-
- edit | object is editable (e.g. TEXT or parts of a path)
- this probably is hardest to implement, and
- left for the future |
-
-
-
-
-### Use of capabilities with selections and during transformations
-
-When multiple `GfxObject`s are selected, the capability bitmasks could
-be `and`ed to obtain the greatest common denominator. For example,
-objects may be movable, but not rotateable, and then the rotate handle
-is omitted in the GUI. Similarly, isotropic-only scaling would leave
-only the corner handles, whereas anisotropic scaling would also
-display handles in the middle of the sides (see
-[here](#selection-wireframe) for selection wireframe).
-
-During transform operations, `draw_transformed` is called for each
-object, with a 2D affine transformation matrix as argument.
-
-At the end of the operation, `transform` is called with the final
-transformation matrix, which then modifies the object in the object
-store.
-
-The 2D affine transformation functions are already implemented in
-`coordlist_ops.c`.
-
-## Actions
-
-When calling `do`, the `action` argument selects the task to be performed.
-
-
-
- draw | draw object in "normal" state |
-
-
- draw_selected | draw object in "selected" state |
-
-
- draw_transformed | draw object in intermediate "transformed" state;
- this allows to display the object dynamically,
- e.g. while it is being dragged |
-
-
- is_clicked | detect whether a click at coordinate (x,y)
- "hits" the object, for example to select it |
-
-
- transform | transforms the object, modifying the context
- in the gfx_store |
-
-
- destroy | destroys the object, deallocating memory |
-
-
-
-
-## Drawing of objects store
-
-Whenever objects need to be re-drawn, the `redraw` function simply iterates
-through the object store and calls `object->do(ptr, draw_action)` for
-all objects.
-
-To improve update speed, a cached `cairo_surface_t` (similar to
-`backbuffer`) is maintained. This cache contains all drawing
-operations up to but not including the first that is flagged as
-`dynamic`.
-
-In practice, most of the time only the last object would need to be
-redrawn atop the cached image. This is no different from the current
-code which often draws over `aux_backbuffer`.
-
-# Migration to new drawing system
-
-## First step
-
-* **Object store**
-
- The object store is added to `GromitData`, together with some
- housekeeping functions.
-
-* **Callbacks**
-
- Then, the callbacks are adapted to add data to the object store
- instead of `GromitData->coordlist`. Direct drawing is replaced by a
- subsequent call of `redraw`.
-
- - `on_press` adds a new item of the respective type to the object
- store, and "remembers" its `id` (for example, in the hash_table).
-
- - `on_motion` appends coordinates to the respective `GfxObject`, and
- calls `redraw`.
-
- - `on_release` finalizes the operation, for example by smoothing a
- path (SMOOTH tool), and then calling `redraw`.
-
- - for each tool, the specific drawing operation is implemented. The
- only drawing operation that is required at this first stage is the
- drawing of a simple stroke path.
-
-* **Undo/redo**
-
- - "Undo" is implemented by simply moving the last `GfxObjects`
- from the object store to the head of a separate "redo" list,
- followed by a call to `redraw`.
-
- - "Redo" moves the first element from the "redo" list to the last
- position in the object store, followed by a call of `redraw`.
-
-These are relatively minor changes. With these, the new architecture
-already is functional.
-
-## Second step
-
-In a next step, a "select" tool is implemented. This requires that the
-`GfxObject` implement `is_clicked` and `draw_selected`.
-
-The select tool draws a wireframe around the selection, showing the
-respective handles (see "capabilities").
-
-Then, a "delete" function is implemented. It basically calls the
-"destroy" method and removes the selected items from the object store,
-and then calls `redraw`.
-
-## Third step
-
-Depending on the capabilities of the selected objects, suitable
-transformations are offered. The simplest one is a "move" handle.
-
-The items that implement "draw_transformed" are dynamically redrawn
-during the "drag" operation. Upon releasing the "move" handle, the
-tool calls the "transform" method, which modifies the coordinates of
-the selecetd items in the object store.
-
-Similarly, "scaling" and "rotation" operations are implemented by
-adding extra handles to the selection wireframe. Everything else
-remains the same.
-
-# Selection wireframe
-
-The selection wireframe could look like this:
-
- R R
- R R
- R +---+ +---+ +---+ R
- | S |------------------| A |------------------| S |
- +---+ +---+ +---+
- | |
- | |
- | |
- | |
- | |
- | ^ |
- +---+ | +---+
- | A | <---M---> | A |
- +---+ | +---+
- | V |
- | |
- | BIN |
- | ICON |
- | |
- | |
- +---+ +---+ +---+
- | S |------------------| A |------------------| S |
- R +---+ +---+ +---+ R
- R R
- R R
-
-| Handle | Function |
-|--------|---------------------------------------------------------------------------|
-| BIN | trash icon, deletes the selected items |
-| M | move handle to drag the selection. SHIFT stays H or V |
-| S | isotropic scaling handle, opposite corner stays where it is |
-| A | anisotropic scaling handle, opposite side stays where it is |
-| R | rotate handle, opposite corner is center of rotation, SHIFT for 45° steps |
diff --git a/doc/architecture/decisions/0001-record-architecture-decisions.md b/doc/architecture/decisions/0001-record-architecture-decisions.md
new file mode 100644
index 0000000..0c2d747
--- /dev/null
+++ b/doc/architecture/decisions/0001-record-architecture-decisions.md
@@ -0,0 +1,19 @@
+# 1. Record architecture decisions
+
+Date: 2024-03-28
+
+## Status
+
+Accepted
+
+## Context
+
+We need to record the architectural decisions made on this project.
+
+## Decision
+
+We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
+
+## Consequences
+
+See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).
diff --git a/doc/architecture/decisions/0002-implement-graphics-object-store.md b/doc/architecture/decisions/0002-implement-graphics-object-store.md
new file mode 100644
index 0000000..7e2799e
--- /dev/null
+++ b/doc/architecture/decisions/0002-implement-graphics-object-store.md
@@ -0,0 +1,423 @@
+# 2. implement graphics object store
+
+Date: 2024-03-28
+
+## Status
+
+Proposed
+
+## Context
+
+`gromit` currently follows a "draw-and-forget" strategy, i.e. each
+drawing operation, once completed, is drawn on a `cairo_surface_t`
+(`GromitData.backbuffer`).
+
+While this is very straightforward, it prevents the implementation of
+
+- **dynamic content**, e.g. items that fade after a certain time and
+ disappear, or otherwise change through time
+
+- **editing** of past content (e.g. select and move or delete an old item)
+
+Currently, a work-around exists in the form of an `aux_backbuffer`,
+which stores the state prior to the drawing operation in
+progress. This allows dynamic content during the ongoing drawing
+operation, and is used in e.g. `LINE`, `SMOOTH`, `RECT`.
+However, the drawing is split up over various callbacks (`on_press`,
+`on_motion`, and `on_release`, which complicates code and maintenance).
+
+Here, I propose to implement a **graphics object store** that contains
+the elements that have been produced so far. By re-executing these
+drawing operations, the widget is (re)painted.
+
+## Implementation
+
+A graphics object store (`objects`) is added to `GromitData`. This
+object store simply is a `GList` of `GfxObject`. Each `GfxObject` has
+the following fields:
+
+ typedef struct {
+ guint id; // unique id
+ GfxType type; // type of object, e.g. Stroke, or Text
+ gboolean dynamic; // content is dynamically updated
+ GromitDeviceData selected; // NULL, or which user selected the item
+ guint32 capabilities; // bit mask with capabilities (see below)
+ BoundingBox extent; // the objects rectangular extent
+ void (*exec)(GfxObject *, action, void *); // object-specific methods
+ } GfxObjectBase;
+
+This basic set of fields is extended by object-specific fields, which
+allows for an inheritance-like pattern, i.e. each specialized object
+can be downcast to the above `GfxObjectBase`.
+
+For a normal stroked path, the specific field could be:
+
+ typedef struct {
+ GfxObjectBase base;
+ GList *coordinates;
+ GdkRGBA color;
+ gfloat arrowsize;
+ ArrowType arrowtype;
+ } GfxObjectPath;
+
+For a text object, one could have:
+
+ typedef struct {
+ GfxObjectBase base;
+ GdkRGBA color;
+ gchar *text;
+ gfloat fontsize;
+ gfloat x, y;
+ } GfxObjectPath;
+
+A path that fades away after a certain time could look like so:
+
+ typedef struct {
+ GfxObjectBase base;
+ GList *coordinates;
+ GdkRGBA color;
+ gfloat arrowsize;
+ ArrowTyype arrowtype;
+ guint64 start_fading; // timestamps in
+ guint64 end_fading; // microseconds
+ } GfxObjectFadingPath;
+
+### Capabilities
+
+Each `gfx_object` contains a bit mask indicating its
+"capabilities". These capabilities imply that certain actions are
+implemented in the `do_action` function.
+
+
+
+ Capability | Description |
+
+
+ draw | object can be drawn
+ this would allow to add non-drawable "information" objects |
+
+
+ draw_transformed | object can be drawn in transformed state;
+ this allows for dynamic representation during operations such as dragging |
+
+
+ move | object can be moved |
+
+
+ isotropic_scale | object can be scaled isotropically |
+
+
+ anisotropic_scale | object can be scaled anisotropically,
+ i.e. differently in X and Y direction isotropic_scale also must be set |
+
+
+ rotate | object can be rotated |
+
+
+ edit | object is editable (e.g. TEXT or parts of a path)
+ this probably is hardest to implement, and left for the future |
+
+
+
+
+#### Use of capabilities with selections and during transformations
+
+When multiple `GfxObject`s are selected, the capability bitmasks could
+be `and`ed to obtain the greatest common denominator. For example,
+objects may be movable, but not rotateable, and then the rotate handle
+is omitted in the GUI. Similarly, isotropic-only scaling would leave
+only the corner handles, whereas anisotropic scaling would also
+display handles in the middle of the sides (see
+[here](#selection-wireframe) for selection wireframe).
+
+During transform operations, `draw_transformed` is called for each
+object, with a 2D affine transformation matrix as argument.
+
+If an object cannot be drawn dynamically in transformed state, for
+example because this is computationally to costly, then the
+transformation is just shown by selection wireframe until the
+operation completed.
+
+At the end of the operation, `transform` is called with the final
+transformation matrix, which then modifies the object in the object
+store.
+
+The 2D affine transformation functions are already implemented in
+`coordlist_ops.c`.
+
+### Actions
+
+When calling `exec`, the `action` argument selects the task to be performed.
+
+
+
+ draw | draw object in "normal" state |
+
+
+ draw_selected | draw object in "selected" state |
+
+
+ draw_transformed | draw object in intermediate "transformed" state;
+ this allows to display the object dynamically, e.g. while it is being dragged |
+
+
+
+ update_bbox | update the bounding box of the base object;
+ trigger recalculation of the bounding box |
+
+
+ get_is_clicked | detect whether a click at coordinate (x,y) "hits" the object, for example to select it |
+
+
+ transform | transforms the object, modifying the context in the gfx_store |
+
+
+ destroy | destroys the object, deallocating memory |
+
+
+ clone | deep-copies the object |
+
+
+
+
+### Drawing of object store
+
+Whenever objects need to be re-drawn, the `draw` function simply
+iterates through the object store and calls `object->do_action(ptr,
+draw_action)` for all objects. Objects with a bounding box not
+intersecting the dirty region (`gdk_cairo_get_clip_rectangle()`) are
+skipped.
+
+### Undo/redo system
+
+Each object that is added or transformed receives a new unique
+`id`. This `id` is delivered by a "factory" function and is a simple
+`guint32` that is incremented at each request.
+
+Maintaining a per-user (i.e. per device) undo system is complicated by
+the fact that an object can be "taken over" by another user by
+manipulating it. This is necessary to enable a truly collaborative
+workflow. At the same time, a user should only undo his/her own
+operations.
+
+To solve this conflict, I propose the following system:
+
+- each device (i.e. user) contains an `GList` with `undo` and `redo` records.
+
+- `GromitData` is extended with an undo object store (`undo_objects`).
+
+- the steps necessary to revert a user's operation are recorded in the
+ respective device's `undo` records.
+
+- an undo operation executes the last undo operation according to the
+ `undo` record of the device, and moves it to the `redo` record.
+
+- a redo operation does the reverse, and moves the record back to the
+ end of the `undo` records.
+
+- any non-undo/redo operation clears the `redo` records.
+
+The `undo` records contain the `id` of the object to move from
+`objects` to `undo_objects`, and the `id` of the object to move from
+`undo_objects` to `objects`. An `id` of zero indicates that there is
+no such object.
+
+- when a `GfxObject` is added, the `undo` record contains the
+ instruction to move the `GfxObject` with the particular `id` from
+ `object`to `undo_objects`.
+
+- when a `GfxObject` is deleted, this object is moved from `objects`
+ to `undo_objects`. The `undo` record consists of the instruction to
+ add the `GfxObject` with the particular `id` back to `objects`.
+
+- when a `GfxObject` is transformed, the object is deep-copied (action
+ `clone`) is created and placed in `undo_objects`. The transformed
+ object remains in `objects`but receives a new `id`. The `undo`
+ record for the respective device contains the instruction to swap
+ the old and new object (which are in the `undo_objects` and the
+ `object_store`, respectively).
+
+Handling of conflicts:
+
+- Conflicts emerge when a user has "taken over" an object from another
+ user. In this case, the objects referenced in the `undo` records no
+ longer exist. In this case, this `undo` record is discarded and the
+ next `undo` record processed.
+
+ Example (here I use letters for the `id`):
+
+ - user 1 adds objects A, B, and C:
+
+```
+ objects -> A -> B -> C -> nil
+
+ undo_objects -> nil
+
+ dev1.undo -> [A->undo] -> [B->undo] -> [C->undo]
+ dev1.redo -> nil
+```
+
+ - user 2 manipulates B, which becomes D:
+
+```
+ objects -> A -> D -> C -> nil
+
+ undo_objects -> B -> nil
+
+ dev1.undo -> [A->undo] -> [B->undo] -> [C->undo] -> nil
+ dev1.redo -> nil
+
+ dev2.undo -> [B->store, D->undo] -> nil
+ dev2.redo -> nil
+```
+
+ - user 1 undos the last step:
+
+
+```
+ objects -> A -> D -> nil
+
+ undo_objects -> B -> C -> nil
+
+ dev1.undo -> [A->undo] -> [B->undo] -> nil
+ dev1.redo -> [C->store] -> nil
+
+ dev2.undo -> [B->store, D->undo] -> nil
+ dev2.redo -> nil
+```
+
+ - user 1 undos one more step, which fails because B is no longer
+ there; this step is discarded and the next step executed.
+
+```
+ objects -> D -> nil
+
+ undo_objects -> B -> C -> A -> nil
+
+ dev1.undo -> nil
+ dev1.redo -> [C->store] -> [A->store] -> nil
+
+ dev2.undo -> [B->store, D->undo] -> nil
+ dev2.redo -> nil
+```
+
+Notes:
+
+- When adding back previously deleted objects from the `undo_objects`,
+ they are placed at the very end of `objects`. This may change the
+ Z-order of the objects.
+
+ **Question**: Should we add two more handles to the selection
+ wireframe? One to move the selection forward, and one to move the
+ selection backwards in Z-order?
+
+- Orphans could occur in the undo store. One possibility would be to
+ reference-count these objects and delete them when the last
+ reference disappears from the undo/redo buffers, but that would
+ require pointers or some related mechanism. The other, simpler,
+ possibility which I prefer is to garbage-collect these once the
+ `undo_objects` exceeds a maximum size.
+
+- When `undo_objects` exceeds a maximum size, the oldest items could
+ be dropped and the `undo`/`redo` records referencing these be deleted.
+
+## Migration to new drawing system
+
+### First step
+
+* **Object store**
+
+ The `objects` store is added to `GromitData`, together with some
+ housekeeping functions.
+
+* **Callbacks**
+
+ Then, the callbacks are adapted to add data to the object store
+ instead of `coordlist`. Direct drawing is replaced by a subsequent
+ call of `draw`.
+
+ - `on_press` adds a new item of the respective type to the object
+ store, and "remembers" its `id` (for example, in the hash_table).
+
+ - `on_motion` appends coordinates to the respective `GfxObject`, and
+ calls `draw`.
+
+ - `on_release` finalizes the operation, for example by smoothing a
+ path (SMOOTH tool), and then calling `draw`.
+
+ - for each tool, the specific drawing operation is implemented. The
+ only drawing operation that is required at this first stage is the
+ drawing of a simple stroke path.
+
+These are relatively minor changes. With these, the new architecture
+already is functional.
+
+### Second step
+
+The "undo/redo" functions are implemented.
+
+### Thirs step
+
+A "select" tool is implemented. This requires that the `GfxObject`
+implement `get_is_clicked` and `draw_selected`.
+
+The select tool draws a wireframe around the selection, showing the
+respective handles (see "capabilities").
+
+Then, a "delete" function is implemented. It basically calls the
+"destroy" method and removes the selected items from the object store,
+and then calls `draw`.
+
+### Fourth step
+
+Depending on the capabilities of the selected objects, suitable
+transformations are offered. The simplest one is a "move" handle.
+
+The items that implement "draw_transformed" are dynamically redrawn
+during the "drag" operation. Upon releasing the "move" handle, the
+tool calls the "transform" method, which modifies the coordinates of
+the selecetd items in the object store.
+
+Similarly, "scaling" and "rotation" operations are implemented by
+adding extra handles to the selection wireframe. Everything else
+remains the same.
+
+## Selection wireframe
+
+The selection wireframe could look like this:
+
+ R R
+ R R
+ R +---+ +---+ +---+ R
+ | S |------------------| A |------------------| S |
+ +---+ +---+ +---+
+ | |
+ | ^ | |
+ | Z Z |
+ | | V |
+ | |
+ | ^ |
+ +---+ | +---+
+ | A | <---M---> | A |
+ +---+ | +---+
+ | V |
+ | |
+ | BIN |
+ | ICON |
+ | |
+ | |
+ +---+ +---+ +---+
+ | S |------------------| A |------------------| S |
+ R +---+ +---+ +---+ R
+ R R
+ R R
+
+| Handle | Function |
+|--------|---------------------------------------------------------------------------|
+| BIN | trash icon, deletes the selected items |
+| M | move handle to drag the selection. SHIFT stays H or V |
+| S | isotropic scaling handle, opposite corner stays where it is |
+| A | anisotropic scaling handle, opposite side stays where it is |
+| R | rotate handle, opposite corner is center of rotation, SHIFT for 45° steps |
+| Z | move up/down selection in Z-order |
+