Index: frontend/node_modules/immer/src/core/current.ts
===================================================================
--- frontend/node_modules/immer/src/core/current.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/core/current.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,60 @@
+import {
+	die,
+	isDraft,
+	shallowCopy,
+	each,
+	DRAFT_STATE,
+	get,
+	set,
+	ImmerState,
+	isDraftable,
+	Archtype,
+	getArchtype,
+	getPlugin
+} from "../internal"
+
+/** Takes a snapshot of the current state of a draft and finalizes it (but without freezing). This is a great utility to print the current state during debugging (no Proxies in the way). The output of current can also be safely leaked outside the producer. */
+export function current<T>(value: T): T
+export function current(value: any): any {
+	if (!isDraft(value)) die(22, value)
+	return currentImpl(value)
+}
+
+function currentImpl(value: any): any {
+	if (!isDraftable(value)) return value
+	const state: ImmerState | undefined = value[DRAFT_STATE]
+	let copy: any
+	const archType = getArchtype(value)
+	if (state) {
+		if (
+			!state.modified_ &&
+			(state.type_ < 4 || !getPlugin("ES5").hasChanges_(state as any))
+		)
+			return state.base_
+		// Optimization: avoid generating new drafts during copying
+		state.finalized_ = true
+		copy = copyHelper(value, archType)
+		state.finalized_ = false
+	} else {
+		copy = copyHelper(value, archType)
+	}
+
+	each(copy, (key, childValue) => {
+		if (state && get(state.base_, key) === childValue) return // no need to copy or search in something that didn't change
+		set(copy, key, currentImpl(childValue))
+	})
+	// In the future, we might consider freezing here, based on the current settings
+	return archType === Archtype.Set ? new Set(copy) : copy
+}
+
+function copyHelper(value: any, archType: number): any {
+	// creates a shallow copy, even if it is a map or set
+	switch (archType) {
+		case Archtype.Map:
+			return new Map(value)
+		case Archtype.Set:
+			// Set will be cloned as array temporarily, so that we can replace individual items
+			return Array.from(value)
+	}
+	return shallowCopy(value)
+}
Index: frontend/node_modules/immer/src/core/finalize.ts
===================================================================
--- frontend/node_modules/immer/src/core/finalize.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/core/finalize.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,168 @@
+import {
+	ImmerScope,
+	DRAFT_STATE,
+	isDraftable,
+	NOTHING,
+	PatchPath,
+	each,
+	has,
+	freeze,
+	ImmerState,
+	isDraft,
+	SetState,
+	set,
+	ProxyType,
+	getPlugin,
+	die,
+	revokeScope,
+	isFrozen,
+	shallowCopy
+} from "../internal"
+
+export function processResult(result: any, scope: ImmerScope) {
+	scope.unfinalizedDrafts_ = scope.drafts_.length
+	const baseDraft = scope.drafts_![0]
+	const isReplaced = result !== undefined && result !== baseDraft
+	if (!scope.immer_.useProxies_)
+		getPlugin("ES5").willFinalizeES5_(scope, result, isReplaced)
+	if (isReplaced) {
+		if (baseDraft[DRAFT_STATE].modified_) {
+			revokeScope(scope)
+			die(4)
+		}
+		if (isDraftable(result)) {
+			// Finalize the result in case it contains (or is) a subset of the draft.
+			result = finalize(scope, result)
+			if (!scope.parent_) maybeFreeze(scope, result)
+		}
+		if (scope.patches_) {
+			getPlugin("Patches").generateReplacementPatches_(
+				baseDraft[DRAFT_STATE].base_,
+				result,
+				scope.patches_,
+				scope.inversePatches_!
+			)
+		}
+	} else {
+		// Finalize the base draft.
+		result = finalize(scope, baseDraft, [])
+	}
+	revokeScope(scope)
+	if (scope.patches_) {
+		scope.patchListener_!(scope.patches_, scope.inversePatches_!)
+	}
+	return result !== NOTHING ? result : undefined
+}
+
+function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) {
+	// Don't recurse in tho recursive data structures
+	if (isFrozen(value)) return value
+
+	const state: ImmerState = value[DRAFT_STATE]
+	// A plain object, might need freezing, might contain drafts
+	if (!state) {
+		each(
+			value,
+			(key, childValue) =>
+				finalizeProperty(rootScope, state, value, key, childValue, path),
+			true // See #590, don't recurse into non-enumerable of non drafted objects
+		)
+		return value
+	}
+	// Never finalize drafts owned by another scope.
+	if (state.scope_ !== rootScope) return value
+	// Unmodified draft, return the (frozen) original
+	if (!state.modified_) {
+		maybeFreeze(rootScope, state.base_, true)
+		return state.base_
+	}
+	// Not finalized yet, let's do that now
+	if (!state.finalized_) {
+		state.finalized_ = true
+		state.scope_.unfinalizedDrafts_--
+		const result =
+			// For ES5, create a good copy from the draft first, with added keys and without deleted keys.
+			state.type_ === ProxyType.ES5Object || state.type_ === ProxyType.ES5Array
+				? (state.copy_ = shallowCopy(state.draft_))
+				: state.copy_
+		// Finalize all children of the copy
+		// For sets we clone before iterating, otherwise we can get in endless loop due to modifying during iteration, see #628
+		// To preserve insertion order in all cases we then clear the set
+		// And we let finalizeProperty know it needs to re-add non-draft children back to the target
+		let resultEach = result
+		let isSet = false
+		if (state.type_ === ProxyType.Set) {
+			resultEach = new Set(result)
+			result.clear()
+			isSet = true
+		}
+		each(resultEach, (key, childValue) =>
+			finalizeProperty(rootScope, state, result, key, childValue, path, isSet)
+		)
+		// everything inside is frozen, we can freeze here
+		maybeFreeze(rootScope, result, false)
+		// first time finalizing, let's create those patches
+		if (path && rootScope.patches_) {
+			getPlugin("Patches").generatePatches_(
+				state,
+				path,
+				rootScope.patches_,
+				rootScope.inversePatches_!
+			)
+		}
+	}
+	return state.copy_
+}
+
+function finalizeProperty(
+	rootScope: ImmerScope,
+	parentState: undefined | ImmerState,
+	targetObject: any,
+	prop: string | number,
+	childValue: any,
+	rootPath?: PatchPath,
+	targetIsSet?: boolean
+) {
+	if (__DEV__ && childValue === targetObject) die(5)
+	if (isDraft(childValue)) {
+		const path =
+			rootPath &&
+			parentState &&
+			parentState!.type_ !== ProxyType.Set && // Set objects are atomic since they have no keys.
+			!has((parentState as Exclude<ImmerState, SetState>).assigned_!, prop) // Skip deep patches for assigned keys.
+				? rootPath!.concat(prop)
+				: undefined
+		// Drafts owned by `scope` are finalized here.
+		const res = finalize(rootScope, childValue, path)
+		set(targetObject, prop, res)
+		// Drafts from another scope must prevented to be frozen
+		// if we got a draft back from finalize, we're in a nested produce and shouldn't freeze
+		if (isDraft(res)) {
+			rootScope.canAutoFreeze_ = false
+		} else return
+	} else if (targetIsSet) {
+		targetObject.add(childValue)
+	}
+	// Search new objects for unfinalized drafts. Frozen objects should never contain drafts.
+	if (isDraftable(childValue) && !isFrozen(childValue)) {
+		if (!rootScope.immer_.autoFreeze_ && rootScope.unfinalizedDrafts_ < 1) {
+			// optimization: if an object is not a draft, and we don't have to
+			// deepfreeze everything, and we are sure that no drafts are left in the remaining object
+			// cause we saw and finalized all drafts already; we can stop visiting the rest of the tree.
+			// This benefits especially adding large data tree's without further processing.
+			// See add-data.js perf test
+			return
+		}
+		finalize(rootScope, childValue)
+		// immer deep freezes plain objects, so if there is no parent state, we freeze as well
+		if (!parentState || !parentState.scope_.parent_)
+			maybeFreeze(rootScope, childValue)
+	}
+}
+
+function maybeFreeze(scope: ImmerScope, value: any, deep = false) {
+	// we never freeze for a non-root scope; as it would prevent pruning for drafts inside wrapping objects
+	if (!scope.parent_ && scope.immer_.autoFreeze_ && scope.canAutoFreeze_) {
+		freeze(value, deep)
+	}
+}
Index: frontend/node_modules/immer/src/core/immerClass.ts
===================================================================
--- frontend/node_modules/immer/src/core/immerClass.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/core/immerClass.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,241 @@
+import {
+	IProduceWithPatches,
+	IProduce,
+	ImmerState,
+	Drafted,
+	isDraftable,
+	processResult,
+	Patch,
+	Objectish,
+	DRAFT_STATE,
+	Draft,
+	PatchListener,
+	isDraft,
+	isMap,
+	isSet,
+	createProxyProxy,
+	getPlugin,
+	die,
+	hasProxies,
+	enterScope,
+	revokeScope,
+	leaveScope,
+	usePatchesInScope,
+	getCurrentScope,
+	NOTHING,
+	freeze,
+	current
+} from "../internal"
+
+interface ProducersFns {
+	produce: IProduce
+	produceWithPatches: IProduceWithPatches
+}
+
+export class Immer implements ProducersFns {
+	useProxies_: boolean = hasProxies
+
+	autoFreeze_: boolean = true
+
+	constructor(config?: {useProxies?: boolean; autoFreeze?: boolean}) {
+		if (typeof config?.useProxies === "boolean")
+			this.setUseProxies(config!.useProxies)
+		if (typeof config?.autoFreeze === "boolean")
+			this.setAutoFreeze(config!.autoFreeze)
+	}
+
+	/**
+	 * The `produce` function takes a value and a "recipe function" (whose
+	 * return value often depends on the base state). The recipe function is
+	 * free to mutate its first argument however it wants. All mutations are
+	 * only ever applied to a __copy__ of the base state.
+	 *
+	 * Pass only a function to create a "curried producer" which relieves you
+	 * from passing the recipe function every time.
+	 *
+	 * Only plain objects and arrays are made mutable. All other objects are
+	 * considered uncopyable.
+	 *
+	 * Note: This function is __bound__ to its `Immer` instance.
+	 *
+	 * @param {any} base - the initial state
+	 * @param {Function} recipe - function that receives a proxy of the base state as first argument and which can be freely modified
+	 * @param {Function} patchListener - optional function that will be called with all the patches produced here
+	 * @returns {any} a new state, or the initial state if nothing was modified
+	 */
+	produce: IProduce = (base: any, recipe?: any, patchListener?: any) => {
+		// curried invocation
+		if (typeof base === "function" && typeof recipe !== "function") {
+			const defaultBase = recipe
+			recipe = base
+
+			const self = this
+			return function curriedProduce(
+				this: any,
+				base = defaultBase,
+				...args: any[]
+			) {
+				return self.produce(base, (draft: Drafted) => recipe.call(this, draft, ...args)) // prettier-ignore
+			}
+		}
+
+		if (typeof recipe !== "function") die(6)
+		if (patchListener !== undefined && typeof patchListener !== "function")
+			die(7)
+
+		let result
+
+		// Only plain objects, arrays, and "immerable classes" are drafted.
+		if (isDraftable(base)) {
+			const scope = enterScope(this)
+			const proxy = createProxy(this, base, undefined)
+			let hasError = true
+			try {
+				result = recipe(proxy)
+				hasError = false
+			} finally {
+				// finally instead of catch + rethrow better preserves original stack
+				if (hasError) revokeScope(scope)
+				else leaveScope(scope)
+			}
+			if (typeof Promise !== "undefined" && result instanceof Promise) {
+				return result.then(
+					result => {
+						usePatchesInScope(scope, patchListener)
+						return processResult(result, scope)
+					},
+					error => {
+						revokeScope(scope)
+						throw error
+					}
+				)
+			}
+			usePatchesInScope(scope, patchListener)
+			return processResult(result, scope)
+		} else if (!base || typeof base !== "object") {
+			result = recipe(base)
+			if (result === undefined) result = base
+			if (result === NOTHING) result = undefined
+			if (this.autoFreeze_) freeze(result, true)
+			if (patchListener) {
+				const p: Patch[] = []
+				const ip: Patch[] = []
+				getPlugin("Patches").generateReplacementPatches_(base, result, p, ip)
+				patchListener(p, ip)
+			}
+			return result
+		} else die(21, base)
+	}
+
+	produceWithPatches: IProduceWithPatches = (base: any, recipe?: any): any => {
+		// curried invocation
+		if (typeof base === "function") {
+			return (state: any, ...args: any[]) =>
+				this.produceWithPatches(state, (draft: any) => base(draft, ...args))
+		}
+
+		let patches: Patch[], inversePatches: Patch[]
+		const result = this.produce(base, recipe, (p: Patch[], ip: Patch[]) => {
+			patches = p
+			inversePatches = ip
+		})
+
+		if (typeof Promise !== "undefined" && result instanceof Promise) {
+			return result.then(nextState => [nextState, patches!, inversePatches!])
+		}
+		return [result, patches!, inversePatches!]
+	}
+
+	createDraft<T extends Objectish>(base: T): Draft<T> {
+		if (!isDraftable(base)) die(8)
+		if (isDraft(base)) base = current(base)
+		const scope = enterScope(this)
+		const proxy = createProxy(this, base, undefined)
+		proxy[DRAFT_STATE].isManual_ = true
+		leaveScope(scope)
+		return proxy as any
+	}
+
+	finishDraft<D extends Draft<any>>(
+		draft: D,
+		patchListener?: PatchListener
+	): D extends Draft<infer T> ? T : never {
+		const state: ImmerState = draft && (draft as any)[DRAFT_STATE]
+		if (__DEV__) {
+			if (!state || !state.isManual_) die(9)
+			if (state.finalized_) die(10)
+		}
+		const {scope_: scope} = state
+		usePatchesInScope(scope, patchListener)
+		return processResult(undefined, scope)
+	}
+
+	/**
+	 * Pass true to automatically freeze all copies created by Immer.
+	 *
+	 * By default, auto-freezing is enabled.
+	 */
+	setAutoFreeze(value: boolean) {
+		this.autoFreeze_ = value
+	}
+
+	/**
+	 * Pass true to use the ES2015 `Proxy` class when creating drafts, which is
+	 * always faster than using ES5 proxies.
+	 *
+	 * By default, feature detection is used, so calling this is rarely necessary.
+	 */
+	setUseProxies(value: boolean) {
+		if (value && !hasProxies) {
+			die(20)
+		}
+		this.useProxies_ = value
+	}
+
+	applyPatches<T extends Objectish>(base: T, patches: Patch[]): T {
+		// If a patch replaces the entire state, take that replacement as base
+		// before applying patches
+		let i: number
+		for (i = patches.length - 1; i >= 0; i--) {
+			const patch = patches[i]
+			if (patch.path.length === 0 && patch.op === "replace") {
+				base = patch.value
+				break
+			}
+		}
+		// If there was a patch that replaced the entire state, start from the
+		// patch after that.
+		if (i > -1) {
+			patches = patches.slice(i + 1)
+		}
+
+		const applyPatchesImpl = getPlugin("Patches").applyPatches_
+		if (isDraft(base)) {
+			// N.B: never hits if some patch a replacement, patches are never drafts
+			return applyPatchesImpl(base, patches)
+		}
+		// Otherwise, produce a copy of the base state.
+		return this.produce(base, (draft: Drafted) =>
+			applyPatchesImpl(draft, patches)
+		)
+	}
+}
+
+export function createProxy<T extends Objectish>(
+	immer: Immer,
+	value: T,
+	parent?: ImmerState
+): Drafted<T, ImmerState> {
+	// precondition: createProxy should be guarded by isDraftable, so we know we can safely draft
+	const draft: Drafted = isMap(value)
+		? getPlugin("MapSet").proxyMap_(value, parent)
+		: isSet(value)
+		? getPlugin("MapSet").proxySet_(value, parent)
+		: immer.useProxies_
+		? createProxyProxy(value, parent)
+		: getPlugin("ES5").createES5Proxy_(value, parent)
+
+	const scope = parent ? parent.scope_ : getCurrentScope()
+	scope.drafts_.push(draft)
+	return draft
+}
Index: frontend/node_modules/immer/src/core/proxy.ts
===================================================================
--- frontend/node_modules/immer/src/core/proxy.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/core/proxy.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,280 @@
+import {
+	each,
+	has,
+	is,
+	isDraftable,
+	shallowCopy,
+	latest,
+	ImmerBaseState,
+	ImmerState,
+	Drafted,
+	AnyObject,
+	AnyArray,
+	Objectish,
+	getCurrentScope,
+	DRAFT_STATE,
+	die,
+	createProxy,
+	ProxyType
+} from "../internal"
+
+interface ProxyBaseState extends ImmerBaseState {
+	assigned_: {
+		[property: string]: boolean
+	}
+	parent_?: ImmerState
+	revoke_(): void
+}
+
+export interface ProxyObjectState extends ProxyBaseState {
+	type_: ProxyType.ProxyObject
+	base_: any
+	copy_: any
+	draft_: Drafted<AnyObject, ProxyObjectState>
+}
+
+export interface ProxyArrayState extends ProxyBaseState {
+	type_: ProxyType.ProxyArray
+	base_: AnyArray
+	copy_: AnyArray | null
+	draft_: Drafted<AnyArray, ProxyArrayState>
+}
+
+type ProxyState = ProxyObjectState | ProxyArrayState
+
+/**
+ * Returns a new draft of the `base` object.
+ *
+ * The second argument is the parent draft-state (used internally).
+ */
+export function createProxyProxy<T extends Objectish>(
+	base: T,
+	parent?: ImmerState
+): Drafted<T, ProxyState> {
+	const isArray = Array.isArray(base)
+	const state: ProxyState = {
+		type_: isArray ? ProxyType.ProxyArray : (ProxyType.ProxyObject as any),
+		// Track which produce call this is associated with.
+		scope_: parent ? parent.scope_ : getCurrentScope()!,
+		// True for both shallow and deep changes.
+		modified_: false,
+		// Used during finalization.
+		finalized_: false,
+		// Track which properties have been assigned (true) or deleted (false).
+		assigned_: {},
+		// The parent draft state.
+		parent_: parent,
+		// The base state.
+		base_: base,
+		// The base proxy.
+		draft_: null as any, // set below
+		// The base copy with any updated values.
+		copy_: null,
+		// Called by the `produce` function.
+		revoke_: null as any,
+		isManual_: false
+	}
+
+	// the traps must target something, a bit like the 'real' base.
+	// but also, we need to be able to determine from the target what the relevant state is
+	// (to avoid creating traps per instance to capture the state in closure,
+	// and to avoid creating weird hidden properties as well)
+	// So the trick is to use 'state' as the actual 'target'! (and make sure we intercept everything)
+	// Note that in the case of an array, we put the state in an array to have better Reflect defaults ootb
+	let target: T = state as any
+	let traps: ProxyHandler<object | Array<any>> = objectTraps
+	if (isArray) {
+		target = [state] as any
+		traps = arrayTraps
+	}
+
+	const {revoke, proxy} = Proxy.revocable(target, traps)
+	state.draft_ = proxy as any
+	state.revoke_ = revoke
+	return proxy as any
+}
+
+/**
+ * Object drafts
+ */
+export const objectTraps: ProxyHandler<ProxyState> = {
+	get(state, prop) {
+		if (prop === DRAFT_STATE) return state
+
+		const source = latest(state)
+		if (!has(source, prop)) {
+			// non-existing or non-own property...
+			return readPropFromProto(state, source, prop)
+		}
+		const value = source[prop]
+		if (state.finalized_ || !isDraftable(value)) {
+			return value
+		}
+		// Check for existing draft in modified state.
+		// Assigned values are never drafted. This catches any drafts we created, too.
+		if (value === peek(state.base_, prop)) {
+			prepareCopy(state)
+			return (state.copy_![prop as any] = createProxy(
+				state.scope_.immer_,
+				value,
+				state
+			))
+		}
+		return value
+	},
+	has(state, prop) {
+		return prop in latest(state)
+	},
+	ownKeys(state) {
+		return Reflect.ownKeys(latest(state))
+	},
+	set(
+		state: ProxyObjectState,
+		prop: string /* strictly not, but helps TS */,
+		value
+	) {
+		const desc = getDescriptorFromProto(latest(state), prop)
+		if (desc?.set) {
+			// special case: if this write is captured by a setter, we have
+			// to trigger it with the correct context
+			desc.set.call(state.draft_, value)
+			return true
+		}
+		if (!state.modified_) {
+			// the last check is because we need to be able to distinguish setting a non-existing to undefined (which is a change)
+			// from setting an existing property with value undefined to undefined (which is not a change)
+			const current = peek(latest(state), prop)
+			// special case, if we assigning the original value to a draft, we can ignore the assignment
+			const currentState: ProxyObjectState = current?.[DRAFT_STATE]
+			if (currentState && currentState.base_ === value) {
+				state.copy_![prop] = value
+				state.assigned_[prop] = false
+				return true
+			}
+			if (is(value, current) && (value !== undefined || has(state.base_, prop)))
+				return true
+			prepareCopy(state)
+			markChanged(state)
+		}
+
+		if (
+			(state.copy_![prop] === value &&
+				// special case: handle new props with value 'undefined'
+				(value !== undefined || prop in state.copy_)) ||
+			// special case: NaN
+			(Number.isNaN(value) && Number.isNaN(state.copy_![prop]))
+		)
+			return true
+
+		// @ts-ignore
+		state.copy_![prop] = value
+		state.assigned_[prop] = true
+		return true
+	},
+	deleteProperty(state, prop: string) {
+		// The `undefined` check is a fast path for pre-existing keys.
+		if (peek(state.base_, prop) !== undefined || prop in state.base_) {
+			state.assigned_[prop] = false
+			prepareCopy(state)
+			markChanged(state)
+		} else {
+			// if an originally not assigned property was deleted
+			delete state.assigned_[prop]
+		}
+		// @ts-ignore
+		if (state.copy_) delete state.copy_[prop]
+		return true
+	},
+	// Note: We never coerce `desc.value` into an Immer draft, because we can't make
+	// the same guarantee in ES5 mode.
+	getOwnPropertyDescriptor(state, prop) {
+		const owner = latest(state)
+		const desc = Reflect.getOwnPropertyDescriptor(owner, prop)
+		if (!desc) return desc
+		return {
+			writable: true,
+			configurable: state.type_ !== ProxyType.ProxyArray || prop !== "length",
+			enumerable: desc.enumerable,
+			value: owner[prop]
+		}
+	},
+	defineProperty() {
+		die(11)
+	},
+	getPrototypeOf(state) {
+		return Object.getPrototypeOf(state.base_)
+	},
+	setPrototypeOf() {
+		die(12)
+	}
+}
+
+/**
+ * Array drafts
+ */
+
+const arrayTraps: ProxyHandler<[ProxyArrayState]> = {}
+each(objectTraps, (key, fn) => {
+	// @ts-ignore
+	arrayTraps[key] = function() {
+		arguments[0] = arguments[0][0]
+		return fn.apply(this, arguments)
+	}
+})
+arrayTraps.deleteProperty = function(state, prop) {
+	if (__DEV__ && isNaN(parseInt(prop as any))) die(13)
+	// @ts-ignore
+	return arrayTraps.set!.call(this, state, prop, undefined)
+}
+arrayTraps.set = function(state, prop, value) {
+	if (__DEV__ && prop !== "length" && isNaN(parseInt(prop as any))) die(14)
+	return objectTraps.set!.call(this, state[0], prop, value, state[0])
+}
+
+// Access a property without creating an Immer draft.
+function peek(draft: Drafted, prop: PropertyKey) {
+	const state = draft[DRAFT_STATE]
+	const source = state ? latest(state) : draft
+	return source[prop]
+}
+
+function readPropFromProto(state: ImmerState, source: any, prop: PropertyKey) {
+	const desc = getDescriptorFromProto(source, prop)
+	return desc
+		? `value` in desc
+			? desc.value
+			: // This is a very special case, if the prop is a getter defined by the
+			  // prototype, we should invoke it with the draft as context!
+			  desc.get?.call(state.draft_)
+		: undefined
+}
+
+function getDescriptorFromProto(
+	source: any,
+	prop: PropertyKey
+): PropertyDescriptor | undefined {
+	// 'in' checks proto!
+	if (!(prop in source)) return undefined
+	let proto = Object.getPrototypeOf(source)
+	while (proto) {
+		const desc = Object.getOwnPropertyDescriptor(proto, prop)
+		if (desc) return desc
+		proto = Object.getPrototypeOf(proto)
+	}
+	return undefined
+}
+
+export function markChanged(state: ImmerState) {
+	if (!state.modified_) {
+		state.modified_ = true
+		if (state.parent_) {
+			markChanged(state.parent_)
+		}
+	}
+}
+
+export function prepareCopy(state: {base_: any; copy_: any}) {
+	if (!state.copy_) {
+		state.copy_ = shallowCopy(state.base_)
+	}
+}
Index: frontend/node_modules/immer/src/core/scope.ts
===================================================================
--- frontend/node_modules/immer/src/core/scope.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/core/scope.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,85 @@
+import {
+	Patch,
+	PatchListener,
+	Drafted,
+	Immer,
+	DRAFT_STATE,
+	ImmerState,
+	ProxyType,
+	getPlugin
+} from "../internal"
+import {die} from "../utils/errors"
+
+/** Each scope represents a `produce` call. */
+
+export interface ImmerScope {
+	patches_?: Patch[]
+	inversePatches_?: Patch[]
+	canAutoFreeze_: boolean
+	drafts_: any[]
+	parent_?: ImmerScope
+	patchListener_?: PatchListener
+	immer_: Immer
+	unfinalizedDrafts_: number
+}
+
+let currentScope: ImmerScope | undefined
+
+export function getCurrentScope() {
+	if (__DEV__ && !currentScope) die(0)
+	return currentScope!
+}
+
+function createScope(
+	parent_: ImmerScope | undefined,
+	immer_: Immer
+): ImmerScope {
+	return {
+		drafts_: [],
+		parent_,
+		immer_,
+		// Whenever the modified draft contains a draft from another scope, we
+		// need to prevent auto-freezing so the unowned draft can be finalized.
+		canAutoFreeze_: true,
+		unfinalizedDrafts_: 0
+	}
+}
+
+export function usePatchesInScope(
+	scope: ImmerScope,
+	patchListener?: PatchListener
+) {
+	if (patchListener) {
+		getPlugin("Patches") // assert we have the plugin
+		scope.patches_ = []
+		scope.inversePatches_ = []
+		scope.patchListener_ = patchListener
+	}
+}
+
+export function revokeScope(scope: ImmerScope) {
+	leaveScope(scope)
+	scope.drafts_.forEach(revokeDraft)
+	// @ts-ignore
+	scope.drafts_ = null
+}
+
+export function leaveScope(scope: ImmerScope) {
+	if (scope === currentScope) {
+		currentScope = scope.parent_
+	}
+}
+
+export function enterScope(immer: Immer) {
+	return (currentScope = createScope(currentScope, immer))
+}
+
+function revokeDraft(draft: Drafted) {
+	const state: ImmerState = draft[DRAFT_STATE]
+	if (
+		state.type_ === ProxyType.ProxyObject ||
+		state.type_ === ProxyType.ProxyArray
+	)
+		state.revoke_()
+	else state.revoked_ = true
+}
Index: frontend/node_modules/immer/src/immer.ts
===================================================================
--- frontend/node_modules/immer/src/immer.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/immer.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,117 @@
+import {
+	IProduce,
+	IProduceWithPatches,
+	Immer,
+	Draft,
+	Immutable
+} from "./internal"
+
+export {
+	Draft,
+	Immutable,
+	Patch,
+	PatchListener,
+	original,
+	current,
+	isDraft,
+	isDraftable,
+	NOTHING as nothing,
+	DRAFTABLE as immerable,
+	freeze
+} from "./internal"
+
+const immer = new Immer()
+
+/**
+ * The `produce` function takes a value and a "recipe function" (whose
+ * return value often depends on the base state). The recipe function is
+ * free to mutate its first argument however it wants. All mutations are
+ * only ever applied to a __copy__ of the base state.
+ *
+ * Pass only a function to create a "curried producer" which relieves you
+ * from passing the recipe function every time.
+ *
+ * Only plain objects and arrays are made mutable. All other objects are
+ * considered uncopyable.
+ *
+ * Note: This function is __bound__ to its `Immer` instance.
+ *
+ * @param {any} base - the initial state
+ * @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
+ * @param {Function} patchListener - optional function that will be called with all the patches produced here
+ * @returns {any} a new state, or the initial state if nothing was modified
+ */
+export const produce: IProduce = immer.produce
+export default produce
+
+/**
+ * Like `produce`, but `produceWithPatches` always returns a tuple
+ * [nextState, patches, inversePatches] (instead of just the next state)
+ */
+export const produceWithPatches: IProduceWithPatches = immer.produceWithPatches.bind(
+	immer
+)
+
+/**
+ * Pass true to automatically freeze all copies created by Immer.
+ *
+ * Always freeze by default, even in production mode
+ */
+export const setAutoFreeze = immer.setAutoFreeze.bind(immer)
+
+/**
+ * Pass true to use the ES2015 `Proxy` class when creating drafts, which is
+ * always faster than using ES5 proxies.
+ *
+ * By default, feature detection is used, so calling this is rarely necessary.
+ */
+export const setUseProxies = immer.setUseProxies.bind(immer)
+
+/**
+ * Apply an array of Immer patches to the first argument.
+ *
+ * This function is a producer, which means copy-on-write is in effect.
+ */
+export const applyPatches = immer.applyPatches.bind(immer)
+
+/**
+ * Create an Immer draft from the given base state, which may be a draft itself.
+ * The draft can be modified until you finalize it with the `finishDraft` function.
+ */
+export const createDraft = immer.createDraft.bind(immer)
+
+/**
+ * Finalize an Immer draft from a `createDraft` call, returning the base state
+ * (if no changes were made) or a modified copy. The draft must *not* be
+ * mutated afterwards.
+ *
+ * Pass a function as the 2nd argument to generate Immer patches based on the
+ * changes that were made.
+ */
+export const finishDraft = immer.finishDraft.bind(immer)
+
+/**
+ * This function is actually a no-op, but can be used to cast an immutable type
+ * to an draft type and make TypeScript happy
+ *
+ * @param value
+ */
+export function castDraft<T>(value: T): Draft<T> {
+	return value as any
+}
+
+/**
+ * This function is actually a no-op, but can be used to cast a mutable type
+ * to an immutable type and make TypeScript happy
+ * @param value
+ */
+export function castImmutable<T>(value: T): Immutable<T> {
+	return value as any
+}
+
+export {Immer}
+
+export {enableES5} from "./plugins/es5"
+export {enablePatches} from "./plugins/patches"
+export {enableMapSet} from "./plugins/mapset"
+export {enableAllPlugins} from "./plugins/all"
Index: frontend/node_modules/immer/src/internal.ts
===================================================================
--- frontend/node_modules/immer/src/internal.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/internal.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,11 @@
+export * from "./utils/env"
+export * from "./utils/errors"
+export * from "./types/types-external"
+export * from "./types/types-internal"
+export * from "./utils/common"
+export * from "./utils/plugins"
+export * from "./core/scope"
+export * from "./core/finalize"
+export * from "./core/proxy"
+export * from "./core/immerClass"
+export * from "./core/current"
Index: frontend/node_modules/immer/src/plugins/all.ts
===================================================================
--- frontend/node_modules/immer/src/plugins/all.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/plugins/all.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+import {enableES5} from "./es5"
+import {enableMapSet} from "./mapset"
+import {enablePatches} from "./patches"
+
+export function enableAllPlugins() {
+	enableES5()
+	enableMapSet()
+	enablePatches()
+}
Index: frontend/node_modules/immer/src/plugins/es5.ts
===================================================================
--- frontend/node_modules/immer/src/plugins/es5.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/plugins/es5.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,277 @@
+import {
+	ImmerState,
+	Drafted,
+	ES5ArrayState,
+	ES5ObjectState,
+	each,
+	has,
+	isDraft,
+	latest,
+	DRAFT_STATE,
+	is,
+	loadPlugin,
+	ImmerScope,
+	ProxyType,
+	getCurrentScope,
+	die,
+	markChanged,
+	objectTraps,
+	ownKeys,
+	getOwnPropertyDescriptors
+} from "../internal"
+
+type ES5State = ES5ArrayState | ES5ObjectState
+
+export function enableES5() {
+	function willFinalizeES5_(
+		scope: ImmerScope,
+		result: any,
+		isReplaced: boolean
+	) {
+		if (!isReplaced) {
+			if (scope.patches_) {
+				markChangesRecursively(scope.drafts_![0])
+			}
+			// This is faster when we don't care about which attributes changed.
+			markChangesSweep(scope.drafts_)
+		}
+		// When a child draft is returned, look for changes.
+		else if (
+			isDraft(result) &&
+			(result[DRAFT_STATE] as ES5State).scope_ === scope
+		) {
+			markChangesSweep(scope.drafts_)
+		}
+	}
+
+	function createES5Draft(isArray: boolean, base: any) {
+		if (isArray) {
+			const draft = new Array(base.length)
+			for (let i = 0; i < base.length; i++)
+				Object.defineProperty(draft, "" + i, proxyProperty(i, true))
+			return draft
+		} else {
+			const descriptors = getOwnPropertyDescriptors(base)
+			delete descriptors[DRAFT_STATE as any]
+			const keys = ownKeys(descriptors)
+			for (let i = 0; i < keys.length; i++) {
+				const key: any = keys[i]
+				descriptors[key] = proxyProperty(
+					key,
+					isArray || !!descriptors[key].enumerable
+				)
+			}
+			return Object.create(Object.getPrototypeOf(base), descriptors)
+		}
+	}
+
+	function createES5Proxy_<T>(
+		base: T,
+		parent?: ImmerState
+	): Drafted<T, ES5ObjectState | ES5ArrayState> {
+		const isArray = Array.isArray(base)
+		const draft = createES5Draft(isArray, base)
+
+		const state: ES5ObjectState | ES5ArrayState = {
+			type_: isArray ? ProxyType.ES5Array : (ProxyType.ES5Object as any),
+			scope_: parent ? parent.scope_ : getCurrentScope(),
+			modified_: false,
+			finalized_: false,
+			assigned_: {},
+			parent_: parent,
+			// base is the object we are drafting
+			base_: base,
+			// draft is the draft object itself, that traps all reads and reads from either the base (if unmodified) or copy (if modified)
+			draft_: draft,
+			copy_: null,
+			revoked_: false,
+			isManual_: false
+		}
+
+		Object.defineProperty(draft, DRAFT_STATE, {
+			value: state,
+			// enumerable: false <- the default
+			writable: true
+		})
+		return draft
+	}
+
+	// property descriptors are recycled to make sure we don't create a get and set closure per property,
+	// but share them all instead
+	const descriptors: {[prop: string]: PropertyDescriptor} = {}
+
+	function proxyProperty(
+		prop: string | number,
+		enumerable: boolean
+	): PropertyDescriptor {
+		let desc = descriptors[prop]
+		if (desc) {
+			desc.enumerable = enumerable
+		} else {
+			descriptors[prop] = desc = {
+				configurable: true,
+				enumerable,
+				get(this: any) {
+					const state = this[DRAFT_STATE]
+					if (__DEV__) assertUnrevoked(state)
+					// @ts-ignore
+					return objectTraps.get(state, prop)
+				},
+				set(this: any, value) {
+					const state = this[DRAFT_STATE]
+					if (__DEV__) assertUnrevoked(state)
+					// @ts-ignore
+					objectTraps.set(state, prop, value)
+				}
+			}
+		}
+		return desc
+	}
+
+	// This looks expensive, but only proxies are visited, and only objects without known changes are scanned.
+	function markChangesSweep(drafts: Drafted<any, ImmerState>[]) {
+		// The natural order of drafts in the `scope` array is based on when they
+		// were accessed. By processing drafts in reverse natural order, we have a
+		// better chance of processing leaf nodes first. When a leaf node is known to
+		// have changed, we can avoid any traversal of its ancestor nodes.
+		for (let i = drafts.length - 1; i >= 0; i--) {
+			const state: ES5State = drafts[i][DRAFT_STATE]
+			if (!state.modified_) {
+				switch (state.type_) {
+					case ProxyType.ES5Array:
+						if (hasArrayChanges(state)) markChanged(state)
+						break
+					case ProxyType.ES5Object:
+						if (hasObjectChanges(state)) markChanged(state)
+						break
+				}
+			}
+		}
+	}
+
+	function markChangesRecursively(object: any) {
+		if (!object || typeof object !== "object") return
+		const state: ES5State | undefined = object[DRAFT_STATE]
+		if (!state) return
+		const {base_, draft_, assigned_, type_} = state
+		if (type_ === ProxyType.ES5Object) {
+			// Look for added keys.
+			// probably there is a faster way to detect changes, as sweep + recurse seems to do some
+			// unnecessary work.
+			// also: probably we can store the information we detect here, to speed up tree finalization!
+			each(draft_, key => {
+				if ((key as any) === DRAFT_STATE) return
+				// The `undefined` check is a fast path for pre-existing keys.
+				if ((base_ as any)[key] === undefined && !has(base_, key)) {
+					assigned_[key] = true
+					markChanged(state)
+				} else if (!assigned_[key]) {
+					// Only untouched properties trigger recursion.
+					markChangesRecursively(draft_[key])
+				}
+			})
+			// Look for removed keys.
+			each(base_, key => {
+				// The `undefined` check is a fast path for pre-existing keys.
+				if (draft_[key] === undefined && !has(draft_, key)) {
+					assigned_[key] = false
+					markChanged(state)
+				}
+			})
+		} else if (type_ === ProxyType.ES5Array) {
+			if (hasArrayChanges(state as ES5ArrayState)) {
+				markChanged(state)
+				assigned_.length = true
+			}
+
+			if (draft_.length < base_.length) {
+				for (let i = draft_.length; i < base_.length; i++) assigned_[i] = false
+			} else {
+				for (let i = base_.length; i < draft_.length; i++) assigned_[i] = true
+			}
+
+			// Minimum count is enough, the other parts has been processed.
+			const min = Math.min(draft_.length, base_.length)
+
+			for (let i = 0; i < min; i++) {
+				// Only untouched indices trigger recursion.
+				if (!draft_.hasOwnProperty(i)) {
+					assigned_[i] = true
+				}
+				if (assigned_[i] === undefined) markChangesRecursively(draft_[i])
+			}
+		}
+	}
+
+	function hasObjectChanges(state: ES5ObjectState) {
+		const {base_, draft_} = state
+
+		// Search for added keys and changed keys. Start at the back, because
+		// non-numeric keys are ordered by time of definition on the object.
+		const keys = ownKeys(draft_)
+		for (let i = keys.length - 1; i >= 0; i--) {
+			const key: any = keys[i]
+			if (key === DRAFT_STATE) continue
+			const baseValue = base_[key]
+			// The `undefined` check is a fast path for pre-existing keys.
+			if (baseValue === undefined && !has(base_, key)) {
+				return true
+			}
+			// Once a base key is deleted, future changes go undetected, because its
+			// descriptor is erased. This branch detects any missed changes.
+			else {
+				const value = draft_[key]
+				const state: ImmerState = value && value[DRAFT_STATE]
+				if (state ? state.base_ !== baseValue : !is(value, baseValue)) {
+					return true
+				}
+			}
+		}
+
+		// At this point, no keys were added or changed.
+		// Compare key count to determine if keys were deleted.
+		const baseIsDraft = !!base_[DRAFT_STATE as any]
+		return keys.length !== ownKeys(base_).length + (baseIsDraft ? 0 : 1) // + 1 to correct for DRAFT_STATE
+	}
+
+	function hasArrayChanges(state: ES5ArrayState) {
+		const {draft_} = state
+		if (draft_.length !== state.base_.length) return true
+		// See #116
+		// If we first shorten the length, our array interceptors will be removed.
+		// If after that new items are added, result in the same original length,
+		// those last items will have no intercepting property.
+		// So if there is no own descriptor on the last position, we know that items were removed and added
+		// N.B.: splice, unshift, etc only shift values around, but not prop descriptors, so we only have to check
+		// the last one
+		// last descriptor can be not a trap, if the array was extended
+		const descriptor = Object.getOwnPropertyDescriptor(
+			draft_,
+			draft_.length - 1
+		)
+		// descriptor can be null, but only for newly created sparse arrays, eg. new Array(10)
+		if (descriptor && !descriptor.get) return true
+		// if we miss a property, it has been deleted, so array probobaly changed
+		for (let i = 0; i < draft_.length; i++) {
+			if (!draft_.hasOwnProperty(i)) return true
+		}
+		// For all other cases, we don't have to compare, as they would have been picked up by the index setters
+		return false
+	}
+
+	function hasChanges_(state: ES5State) {
+		return state.type_ === ProxyType.ES5Object
+			? hasObjectChanges(state)
+			: hasArrayChanges(state)
+	}
+
+	function assertUnrevoked(state: any /*ES5State | MapState | SetState*/) {
+		if (state.revoked_) die(3, JSON.stringify(latest(state)))
+	}
+
+	loadPlugin("ES5", {
+		createES5Proxy_,
+		willFinalizeES5_,
+		hasChanges_
+	})
+}
Index: frontend/node_modules/immer/src/plugins/mapset.ts
===================================================================
--- frontend/node_modules/immer/src/plugins/mapset.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/plugins/mapset.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,348 @@
+// types only!
+import {
+	ImmerState,
+	AnyMap,
+	AnySet,
+	MapState,
+	SetState,
+	DRAFT_STATE,
+	getCurrentScope,
+	latest,
+	iteratorSymbol,
+	isDraftable,
+	createProxy,
+	loadPlugin,
+	markChanged,
+	ProxyType,
+	die,
+	each
+} from "../internal"
+
+export function enableMapSet() {
+	/* istanbul ignore next */
+	var extendStatics = function(d: any, b: any): any {
+		extendStatics =
+			Object.setPrototypeOf ||
+			({__proto__: []} instanceof Array &&
+				function(d, b) {
+					d.__proto__ = b
+				}) ||
+			function(d, b) {
+				for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]
+			}
+		return extendStatics(d, b)
+	}
+
+	// Ugly hack to resolve #502 and inherit built in Map / Set
+	function __extends(d: any, b: any): any {
+		extendStatics(d, b)
+		function __(this: any): any {
+			this.constructor = d
+		}
+		d.prototype =
+			// @ts-ignore
+			((__.prototype = b.prototype), new __())
+	}
+
+	const DraftMap = (function(_super) {
+		__extends(DraftMap, _super)
+		// Create class manually, cause #502
+		function DraftMap(this: any, target: AnyMap, parent?: ImmerState): any {
+			this[DRAFT_STATE] = {
+				type_: ProxyType.Map,
+				parent_: parent,
+				scope_: parent ? parent.scope_ : getCurrentScope()!,
+				modified_: false,
+				finalized_: false,
+				copy_: undefined,
+				assigned_: undefined,
+				base_: target,
+				draft_: this as any,
+				isManual_: false,
+				revoked_: false
+			} as MapState
+			return this
+		}
+		const p = DraftMap.prototype
+
+		Object.defineProperty(p, "size", {
+			get: function() {
+				return latest(this[DRAFT_STATE]).size
+			}
+			// enumerable: false,
+			// configurable: true
+		})
+
+		p.has = function(key: any): boolean {
+			return latest(this[DRAFT_STATE]).has(key)
+		}
+
+		p.set = function(key: any, value: any) {
+			const state: MapState = this[DRAFT_STATE]
+			assertUnrevoked(state)
+			if (!latest(state).has(key) || latest(state).get(key) !== value) {
+				prepareMapCopy(state)
+				markChanged(state)
+				state.assigned_!.set(key, true)
+				state.copy_!.set(key, value)
+				state.assigned_!.set(key, true)
+			}
+			return this
+		}
+
+		p.delete = function(key: any): boolean {
+			if (!this.has(key)) {
+				return false
+			}
+
+			const state: MapState = this[DRAFT_STATE]
+			assertUnrevoked(state)
+			prepareMapCopy(state)
+			markChanged(state)
+			if (state.base_.has(key)) {
+				state.assigned_!.set(key, false)
+			} else {
+				state.assigned_!.delete(key)
+			}
+			state.copy_!.delete(key)
+			return true
+		}
+
+		p.clear = function() {
+			const state: MapState = this[DRAFT_STATE]
+			assertUnrevoked(state)
+			if (latest(state).size) {
+				prepareMapCopy(state)
+				markChanged(state)
+				state.assigned_ = new Map()
+				each(state.base_, key => {
+					state.assigned_!.set(key, false)
+				})
+				state.copy_!.clear()
+			}
+		}
+
+		p.forEach = function(
+			cb: (value: any, key: any, self: any) => void,
+			thisArg?: any
+		) {
+			const state: MapState = this[DRAFT_STATE]
+			latest(state).forEach((_value: any, key: any, _map: any) => {
+				cb.call(thisArg, this.get(key), key, this)
+			})
+		}
+
+		p.get = function(key: any): any {
+			const state: MapState = this[DRAFT_STATE]
+			assertUnrevoked(state)
+			const value = latest(state).get(key)
+			if (state.finalized_ || !isDraftable(value)) {
+				return value
+			}
+			if (value !== state.base_.get(key)) {
+				return value // either already drafted or reassigned
+			}
+			// despite what it looks, this creates a draft only once, see above condition
+			const draft = createProxy(state.scope_.immer_, value, state)
+			prepareMapCopy(state)
+			state.copy_!.set(key, draft)
+			return draft
+		}
+
+		p.keys = function(): IterableIterator<any> {
+			return latest(this[DRAFT_STATE]).keys()
+		}
+
+		p.values = function(): IterableIterator<any> {
+			const iterator = this.keys()
+			return {
+				[iteratorSymbol]: () => this.values(),
+				next: () => {
+					const r = iterator.next()
+					/* istanbul ignore next */
+					if (r.done) return r
+					const value = this.get(r.value)
+					return {
+						done: false,
+						value
+					}
+				}
+			} as any
+		}
+
+		p.entries = function(): IterableIterator<[any, any]> {
+			const iterator = this.keys()
+			return {
+				[iteratorSymbol]: () => this.entries(),
+				next: () => {
+					const r = iterator.next()
+					/* istanbul ignore next */
+					if (r.done) return r
+					const value = this.get(r.value)
+					return {
+						done: false,
+						value: [r.value, value]
+					}
+				}
+			} as any
+		}
+
+		p[iteratorSymbol] = function() {
+			return this.entries()
+		}
+
+		return DraftMap
+	})(Map)
+
+	function proxyMap_<T extends AnyMap>(target: T, parent?: ImmerState): T {
+		// @ts-ignore
+		return new DraftMap(target, parent)
+	}
+
+	function prepareMapCopy(state: MapState) {
+		if (!state.copy_) {
+			state.assigned_ = new Map()
+			state.copy_ = new Map(state.base_)
+		}
+	}
+
+	const DraftSet = (function(_super) {
+		__extends(DraftSet, _super)
+		// Create class manually, cause #502
+		function DraftSet(this: any, target: AnySet, parent?: ImmerState) {
+			this[DRAFT_STATE] = {
+				type_: ProxyType.Set,
+				parent_: parent,
+				scope_: parent ? parent.scope_ : getCurrentScope()!,
+				modified_: false,
+				finalized_: false,
+				copy_: undefined,
+				base_: target,
+				draft_: this,
+				drafts_: new Map(),
+				revoked_: false,
+				isManual_: false
+			} as SetState
+			return this
+		}
+		const p = DraftSet.prototype
+
+		Object.defineProperty(p, "size", {
+			get: function() {
+				return latest(this[DRAFT_STATE]).size
+			}
+			// enumerable: true,
+		})
+
+		p.has = function(value: any): boolean {
+			const state: SetState = this[DRAFT_STATE]
+			assertUnrevoked(state)
+			// bit of trickery here, to be able to recognize both the value, and the draft of its value
+			if (!state.copy_) {
+				return state.base_.has(value)
+			}
+			if (state.copy_.has(value)) return true
+			if (state.drafts_.has(value) && state.copy_.has(state.drafts_.get(value)))
+				return true
+			return false
+		}
+
+		p.add = function(value: any): any {
+			const state: SetState = this[DRAFT_STATE]
+			assertUnrevoked(state)
+			if (!this.has(value)) {
+				prepareSetCopy(state)
+				markChanged(state)
+				state.copy_!.add(value)
+			}
+			return this
+		}
+
+		p.delete = function(value: any): any {
+			if (!this.has(value)) {
+				return false
+			}
+
+			const state: SetState = this[DRAFT_STATE]
+			assertUnrevoked(state)
+			prepareSetCopy(state)
+			markChanged(state)
+			return (
+				state.copy_!.delete(value) ||
+				(state.drafts_.has(value)
+					? state.copy_!.delete(state.drafts_.get(value))
+					: /* istanbul ignore next */ false)
+			)
+		}
+
+		p.clear = function() {
+			const state: SetState = this[DRAFT_STATE]
+			assertUnrevoked(state)
+			if (latest(state).size) {
+				prepareSetCopy(state)
+				markChanged(state)
+				state.copy_!.clear()
+			}
+		}
+
+		p.values = function(): IterableIterator<any> {
+			const state: SetState = this[DRAFT_STATE]
+			assertUnrevoked(state)
+			prepareSetCopy(state)
+			return state.copy_!.values()
+		}
+
+		p.entries = function entries(): IterableIterator<[any, any]> {
+			const state: SetState = this[DRAFT_STATE]
+			assertUnrevoked(state)
+			prepareSetCopy(state)
+			return state.copy_!.entries()
+		}
+
+		p.keys = function(): IterableIterator<any> {
+			return this.values()
+		}
+
+		p[iteratorSymbol] = function() {
+			return this.values()
+		}
+
+		p.forEach = function forEach(cb: any, thisArg?: any) {
+			const iterator = this.values()
+			let result = iterator.next()
+			while (!result.done) {
+				cb.call(thisArg, result.value, result.value, this)
+				result = iterator.next()
+			}
+		}
+
+		return DraftSet
+	})(Set)
+
+	function proxySet_<T extends AnySet>(target: T, parent?: ImmerState): T {
+		// @ts-ignore
+		return new DraftSet(target, parent)
+	}
+
+	function prepareSetCopy(state: SetState) {
+		if (!state.copy_) {
+			// create drafts for all entries to preserve insertion order
+			state.copy_ = new Set()
+			state.base_.forEach(value => {
+				if (isDraftable(value)) {
+					const draft = createProxy(state.scope_.immer_, value, state)
+					state.drafts_.set(value, draft)
+					state.copy_!.add(draft)
+				} else {
+					state.copy_!.add(value)
+				}
+			})
+		}
+	}
+
+	function assertUnrevoked(state: any /*ES5State | MapState | SetState*/) {
+		if (state.revoked_) die(3, JSON.stringify(latest(state)))
+	}
+
+	loadPlugin("MapSet", {proxyMap_, proxySet_})
+}
Index: frontend/node_modules/immer/src/plugins/patches.ts
===================================================================
--- frontend/node_modules/immer/src/plugins/patches.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/plugins/patches.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,305 @@
+import {immerable} from "../immer"
+import {
+	ImmerState,
+	Patch,
+	SetState,
+	ES5ArrayState,
+	ProxyArrayState,
+	MapState,
+	ES5ObjectState,
+	ProxyObjectState,
+	PatchPath,
+	get,
+	each,
+	has,
+	getArchtype,
+	isSet,
+	isMap,
+	loadPlugin,
+	ProxyType,
+	Archtype,
+	die,
+	isDraft,
+	isDraftable,
+	NOTHING
+} from "../internal"
+
+export function enablePatches() {
+	const REPLACE = "replace"
+	const ADD = "add"
+	const REMOVE = "remove"
+
+	function generatePatches_(
+		state: ImmerState,
+		basePath: PatchPath,
+		patches: Patch[],
+		inversePatches: Patch[]
+	): void {
+		switch (state.type_) {
+			case ProxyType.ProxyObject:
+			case ProxyType.ES5Object:
+			case ProxyType.Map:
+				return generatePatchesFromAssigned(
+					state,
+					basePath,
+					patches,
+					inversePatches
+				)
+			case ProxyType.ES5Array:
+			case ProxyType.ProxyArray:
+				return generateArrayPatches(state, basePath, patches, inversePatches)
+			case ProxyType.Set:
+				return generateSetPatches(
+					(state as any) as SetState,
+					basePath,
+					patches,
+					inversePatches
+				)
+		}
+	}
+
+	function generateArrayPatches(
+		state: ES5ArrayState | ProxyArrayState,
+		basePath: PatchPath,
+		patches: Patch[],
+		inversePatches: Patch[]
+	) {
+		let {base_, assigned_} = state
+		let copy_ = state.copy_!
+
+		// Reduce complexity by ensuring `base` is never longer.
+		if (copy_.length < base_.length) {
+			// @ts-ignore
+			;[base_, copy_] = [copy_, base_]
+			;[patches, inversePatches] = [inversePatches, patches]
+		}
+
+		// Process replaced indices.
+		for (let i = 0; i < base_.length; i++) {
+			if (assigned_[i] && copy_[i] !== base_[i]) {
+				const path = basePath.concat([i])
+				patches.push({
+					op: REPLACE,
+					path,
+					// Need to maybe clone it, as it can in fact be the original value
+					// due to the base/copy inversion at the start of this function
+					value: clonePatchValueIfNeeded(copy_[i])
+				})
+				inversePatches.push({
+					op: REPLACE,
+					path,
+					value: clonePatchValueIfNeeded(base_[i])
+				})
+			}
+		}
+
+		// Process added indices.
+		for (let i = base_.length; i < copy_.length; i++) {
+			const path = basePath.concat([i])
+			patches.push({
+				op: ADD,
+				path,
+				// Need to maybe clone it, as it can in fact be the original value
+				// due to the base/copy inversion at the start of this function
+				value: clonePatchValueIfNeeded(copy_[i])
+			})
+		}
+		if (base_.length < copy_.length) {
+			inversePatches.push({
+				op: REPLACE,
+				path: basePath.concat(["length"]),
+				value: base_.length
+			})
+		}
+	}
+
+	// This is used for both Map objects and normal objects.
+	function generatePatchesFromAssigned(
+		state: MapState | ES5ObjectState | ProxyObjectState,
+		basePath: PatchPath,
+		patches: Patch[],
+		inversePatches: Patch[]
+	) {
+		const {base_, copy_} = state
+		each(state.assigned_!, (key, assignedValue) => {
+			const origValue = get(base_, key)
+			const value = get(copy_!, key)
+			const op = !assignedValue ? REMOVE : has(base_, key) ? REPLACE : ADD
+			if (origValue === value && op === REPLACE) return
+			const path = basePath.concat(key as any)
+			patches.push(op === REMOVE ? {op, path} : {op, path, value})
+			inversePatches.push(
+				op === ADD
+					? {op: REMOVE, path}
+					: op === REMOVE
+					? {op: ADD, path, value: clonePatchValueIfNeeded(origValue)}
+					: {op: REPLACE, path, value: clonePatchValueIfNeeded(origValue)}
+			)
+		})
+	}
+
+	function generateSetPatches(
+		state: SetState,
+		basePath: PatchPath,
+		patches: Patch[],
+		inversePatches: Patch[]
+	) {
+		let {base_, copy_} = state
+
+		let i = 0
+		base_.forEach((value: any) => {
+			if (!copy_!.has(value)) {
+				const path = basePath.concat([i])
+				patches.push({
+					op: REMOVE,
+					path,
+					value
+				})
+				inversePatches.unshift({
+					op: ADD,
+					path,
+					value
+				})
+			}
+			i++
+		})
+		i = 0
+		copy_!.forEach((value: any) => {
+			if (!base_.has(value)) {
+				const path = basePath.concat([i])
+				patches.push({
+					op: ADD,
+					path,
+					value
+				})
+				inversePatches.unshift({
+					op: REMOVE,
+					path,
+					value
+				})
+			}
+			i++
+		})
+	}
+
+	function generateReplacementPatches_(
+		baseValue: any,
+		replacement: any,
+		patches: Patch[],
+		inversePatches: Patch[]
+	): void {
+		patches.push({
+			op: REPLACE,
+			path: [],
+			value: replacement === NOTHING ? undefined : replacement
+		})
+		inversePatches.push({
+			op: REPLACE,
+			path: [],
+			value: baseValue
+		})
+	}
+
+	function applyPatches_<T>(draft: T, patches: Patch[]): T {
+		patches.forEach(patch => {
+			const {path, op} = patch
+
+			let base: any = draft
+			for (let i = 0; i < path.length - 1; i++) {
+				const parentType = getArchtype(base)
+				let p = path[i]
+				if (typeof p !== "string" && typeof p !== "number") {
+					p = "" + p
+				}
+
+				// See #738, avoid prototype pollution
+				if (
+					(parentType === Archtype.Object || parentType === Archtype.Array) &&
+					(p === "__proto__" || p === "constructor")
+				)
+					die(24)
+				if (typeof base === "function" && p === "prototype") die(24)
+				base = get(base, p)
+				if (typeof base !== "object") die(15, path.join("/"))
+			}
+
+			const type = getArchtype(base)
+			const value = deepClonePatchValue(patch.value) // used to clone patch to ensure original patch is not modified, see #411
+			const key = path[path.length - 1]
+			switch (op) {
+				case REPLACE:
+					switch (type) {
+						case Archtype.Map:
+							return base.set(key, value)
+						/* istanbul ignore next */
+						case Archtype.Set:
+							die(16)
+						default:
+							// if value is an object, then it's assigned by reference
+							// in the following add or remove ops, the value field inside the patch will also be modifyed
+							// so we use value from the cloned patch
+							// @ts-ignore
+							return (base[key] = value)
+					}
+				case ADD:
+					switch (type) {
+						case Archtype.Array:
+							return key === "-"
+								? base.push(value)
+								: base.splice(key as any, 0, value)
+						case Archtype.Map:
+							return base.set(key, value)
+						case Archtype.Set:
+							return base.add(value)
+						default:
+							return (base[key] = value)
+					}
+				case REMOVE:
+					switch (type) {
+						case Archtype.Array:
+							return base.splice(key as any, 1)
+						case Archtype.Map:
+							return base.delete(key)
+						case Archtype.Set:
+							return base.delete(patch.value)
+						default:
+							return delete base[key]
+					}
+				default:
+					die(17, op)
+			}
+		})
+
+		return draft
+	}
+
+	// optimize: this is quite a performance hit, can we detect intelligently when it is needed?
+	// E.g. auto-draft when new objects from outside are assigned and modified?
+	// (See failing test when deepClone just returns obj)
+	function deepClonePatchValue<T>(obj: T): T
+	function deepClonePatchValue(obj: any) {
+		if (!isDraftable(obj)) return obj
+		if (Array.isArray(obj)) return obj.map(deepClonePatchValue)
+		if (isMap(obj))
+			return new Map(
+				Array.from(obj.entries()).map(([k, v]) => [k, deepClonePatchValue(v)])
+			)
+		if (isSet(obj)) return new Set(Array.from(obj).map(deepClonePatchValue))
+		const cloned = Object.create(Object.getPrototypeOf(obj))
+		for (const key in obj) cloned[key] = deepClonePatchValue(obj[key])
+		if (has(obj, immerable)) cloned[immerable] = obj[immerable]
+		return cloned
+	}
+
+	function clonePatchValueIfNeeded<T>(obj: T): T {
+		if (isDraft(obj)) {
+			return deepClonePatchValue(obj)
+		} else return obj
+	}
+
+	loadPlugin("Patches", {
+		applyPatches_,
+		generatePatches_,
+		generateReplacementPatches_
+	})
+}
Index: frontend/node_modules/immer/src/types/globals.d.ts
===================================================================
--- frontend/node_modules/immer/src/types/globals.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/types/globals.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+declare const __DEV__: boolean
Index: frontend/node_modules/immer/src/types/index.js.flow
===================================================================
--- frontend/node_modules/immer/src/types/index.js.flow	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/types/index.js.flow	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,113 @@
+// @flow
+
+export interface Patch {
+	op: "replace" | "remove" | "add";
+	path: (string | number)[];
+	value?: any;
+}
+
+export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void
+
+type Base = {...} | Array<any>
+interface IProduce {
+	/**
+	 * Immer takes a state, and runs a function against it.
+	 * That function can freely mutate the state, as it will create copies-on-write.
+	 * This means that the original state will stay unchanged, and once the function finishes, the modified state is returned.
+	 *
+	 * If the first argument is a function, this is interpreted as the recipe, and will create a curried function that will execute the recipe
+	 * any time it is called with the current state.
+	 *
+	 * @param currentState - the state to start with
+	 * @param recipe - function that receives a proxy of the current state as first argument and which can be freely modified
+	 * @param initialState - if a curried function is created and this argument was given, it will be used as fallback if the curried function is called with a state of undefined
+	 * @returns The next state: a new state, or the current state if nothing was modified
+	 */
+	<S: Base>(
+		currentState: S,
+		recipe: (draftState: S) => S | void,
+		patchListener?: PatchListener
+	): S;
+	// curried invocations with initial state
+	<S: Base, A = void, B = void, C = void>(
+		recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
+		initialState: S
+	): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => S;
+	// curried invocations without initial state
+	<S: Base, A = void, B = void, C = void>(
+		recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
+	): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S;
+}
+
+interface IProduceWithPatches {
+        /**
+         * Like `produce`, but instead of just returning the new state,
+         * a tuple is returned with [nextState, patches, inversePatches]
+         *
+         * Like produce, this function supports currying
+         */
+	<S: Base>(
+		currentState: S,
+		recipe: (draftState: S) => S | void
+	): [S, Patch[], Patch[]];
+	// curried invocations with initial state
+	<S: Base, A = void, B = void, C = void>(
+		recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
+		initialState: S
+	): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
+	// curried invocations without initial state
+	<S: Base, A = void, B = void, C = void>(
+		recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
+	): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
+}
+
+declare export var produce: IProduce
+declare export default IProduce
+
+declare export var produceWithPatches: IProduceWithPatches
+
+declare export var nothing: typeof undefined
+
+declare export var immerable: Symbol
+
+/**
+ * Automatically freezes any state trees generated by immer.
+ * This protects against accidental modifications of the state tree outside of an immer function.
+ * This comes with a performance impact, so it is recommended to disable this option in production.
+ * By default it is turned on during local development, and turned off in production.
+ */
+declare export function setAutoFreeze(autoFreeze: boolean): void
+
+/**
+ * Manually override whether proxies should be used.
+ * By default done by using feature detection
+ */
+declare export function setUseProxies(useProxies: boolean): void
+
+declare export function applyPatches<S>(state: S, patches: Patch[]): S
+
+declare export function original<S>(value: S): S
+
+declare export function current<S>(value: S): S
+
+declare export function isDraft(value: any): boolean
+
+/**
+ * Creates a mutable draft from an (immutable) object / array.
+ * The draft can be modified until `finishDraft` is called
+ */
+declare export function createDraft<T>(base: T): T
+
+/**
+ * Given a draft that was created using `createDraft`,
+ * finalizes the draft into a new immutable object.
+ * Optionally a patch-listener can be provided to gather the patches that are needed to construct the object.
+ */
+declare export function finishDraft<T>(base: T, listener?: PatchListener): T
+
+declare export function enableES5(): void
+declare export function enableMapSet(): void
+declare export function enablePatches(): void
+declare export function enableAllPlugins(): void
+
+declare export function freeze<T>(obj: T, freeze?: boolean): T
Index: frontend/node_modules/immer/src/types/types-external.ts
===================================================================
--- frontend/node_modules/immer/src/types/types-external.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/types/types-external.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,262 @@
+import {Nothing} from "../internal"
+
+type AnyFunc = (...args: any[]) => any
+
+type PrimitiveType = number | string | boolean
+
+/** Object types that should never be mapped */
+type AtomicObject = Function | Promise<any> | Date | RegExp
+
+/**
+ * If the lib "ES2015.Collection" is not included in tsconfig.json,
+ * types like ReadonlyArray, WeakMap etc. fall back to `any` (specified nowhere)
+ * or `{}` (from the node types), in both cases entering an infinite recursion in
+ * pattern matching type mappings
+ * This type can be used to cast these types to `void` in these cases.
+ */
+export type IfAvailable<T, Fallback = void> =
+	// fallback if any
+	true | false extends (T extends never
+	? true
+	: false)
+		? Fallback // fallback if empty type
+		: keyof T extends never
+		? Fallback // original type
+		: T
+
+/**
+ * These should also never be mapped but must be tested after regular Map and
+ * Set
+ */
+type WeakReferences = IfAvailable<WeakMap<any, any>> | IfAvailable<WeakSet<any>>
+
+export type WritableDraft<T> = {-readonly [K in keyof T]: Draft<T[K]>}
+
+/** Convert a readonly type into a mutable type, if possible */
+export type Draft<T> = T extends PrimitiveType
+	? T
+	: T extends AtomicObject
+	? T
+	: T extends IfAvailable<ReadonlyMap<infer K, infer V>> // Map extends ReadonlyMap
+	? Map<Draft<K>, Draft<V>>
+	: T extends IfAvailable<ReadonlySet<infer V>> // Set extends ReadonlySet
+	? Set<Draft<V>>
+	: T extends WeakReferences
+	? T
+	: T extends object
+	? WritableDraft<T>
+	: T
+
+/** Convert a mutable type into a readonly type */
+export type Immutable<T> = T extends PrimitiveType
+	? T
+	: T extends AtomicObject
+	? T
+	: T extends IfAvailable<ReadonlyMap<infer K, infer V>> // Map extends ReadonlyMap
+	? ReadonlyMap<Immutable<K>, Immutable<V>>
+	: T extends IfAvailable<ReadonlySet<infer V>> // Set extends ReadonlySet
+	? ReadonlySet<Immutable<V>>
+	: T extends WeakReferences
+	? T
+	: T extends object
+	? {readonly [K in keyof T]: Immutable<T[K]>}
+	: T
+
+export interface Patch {
+	op: "replace" | "remove" | "add"
+	path: (string | number)[]
+	value?: any
+}
+
+export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void
+
+/** Converts `nothing` into `undefined` */
+type FromNothing<T> = T extends Nothing ? undefined : T
+
+/** The inferred return type of `produce` */
+export type Produced<Base, Return> = Return extends void
+	? Base
+	: Return extends Promise<infer Result>
+	? Promise<Result extends void ? Base : FromNothing<Result>>
+	: FromNothing<Return>
+
+/**
+ * Utility types
+ */
+type PatchesTuple<T> = readonly [T, Patch[], Patch[]]
+
+type ValidRecipeReturnType<State> =
+	| State
+	| void
+	| undefined
+	| (State extends undefined ? Nothing : never)
+
+type ValidRecipeReturnTypePossiblyPromise<State> =
+	| ValidRecipeReturnType<State>
+	| Promise<ValidRecipeReturnType<State>>
+
+type PromisifyReturnIfNeeded<
+	State,
+	Recipe extends AnyFunc,
+	UsePatches extends boolean
+> = ReturnType<Recipe> extends Promise<any>
+	? Promise<UsePatches extends true ? PatchesTuple<State> : State>
+	: UsePatches extends true
+	? PatchesTuple<State>
+	: State
+
+/**
+ * Core Producer inference
+ */
+type InferRecipeFromCurried<Curried> = Curried extends (
+	base: infer State,
+	...rest: infer Args
+) => any // extra assertion to make sure this is a proper curried function (state, args) => state
+	? ReturnType<Curried> extends State
+		? (
+				draft: Draft<State>,
+				...rest: Args
+		  ) => ValidRecipeReturnType<Draft<State>>
+		: never
+	: never
+
+type InferInitialStateFromCurried<Curried> = Curried extends (
+	base: infer State,
+	...rest: any[]
+) => any // extra assertion to make sure this is a proper curried function (state, args) => state
+	? State
+	: never
+
+type InferCurriedFromRecipe<
+	Recipe,
+	UsePatches extends boolean
+> = Recipe extends (draft: infer DraftState, ...args: infer RestArgs) => any // verify return type
+	? ReturnType<Recipe> extends ValidRecipeReturnTypePossiblyPromise<DraftState>
+		? (
+				base: Immutable<DraftState>,
+				...args: RestArgs
+		  ) => PromisifyReturnIfNeeded<DraftState, Recipe, UsePatches> // N.b. we return mutable draftstate, in case the recipe's first arg isn't read only, and that isn't expected as output either
+		: never // incorrect return type
+	: never // not a function
+
+type InferCurriedFromInitialStateAndRecipe<
+	State,
+	Recipe,
+	UsePatches extends boolean
+> = Recipe extends (
+	draft: Draft<State>,
+	...rest: infer RestArgs
+) => ValidRecipeReturnTypePossiblyPromise<State>
+	? (
+			base?: State | undefined,
+			...args: RestArgs
+	  ) => PromisifyReturnIfNeeded<State, Recipe, UsePatches>
+	: never // recipe doesn't match initial state
+
+/**
+ * The `produce` function takes a value and a "recipe function" (whose
+ * return value often depends on the base state). The recipe function is
+ * free to mutate its first argument however it wants. All mutations are
+ * only ever applied to a __copy__ of the base state.
+ *
+ * Pass only a function to create a "curried producer" which relieves you
+ * from passing the recipe function every time.
+ *
+ * Only plain objects and arrays are made mutable. All other objects are
+ * considered uncopyable.
+ *
+ * Note: This function is __bound__ to its `Immer` instance.
+ *
+ * @param {any} base - the initial state
+ * @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
+ * @param {Function} patchListener - optional function that will be called with all the patches produced here
+ * @returns {any} a new state, or the initial state if nothing was modified
+ */
+export interface IProduce {
+	/** Curried producer that infers the recipe from the curried output function (e.g. when passing to setState) */
+	<Curried>(
+		recipe: InferRecipeFromCurried<Curried>,
+		initialState?: InferInitialStateFromCurried<Curried>
+	): Curried
+
+	/** Curried producer that infers curried from the recipe  */
+	<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<
+		Recipe,
+		false
+	>
+
+	/** Curried producer that infers curried from the State generic, which is explicitly passed in.  */
+	<State>(
+		recipe: (
+			state: Draft<State>,
+			initialState: State
+		) => ValidRecipeReturnType<State>
+	): (state?: State) => State
+	<State, Args extends any[]>(
+		recipe: (
+			state: Draft<State>,
+			...args: Args
+		) => ValidRecipeReturnType<State>,
+		initialState: State
+	): (state?: State, ...args: Args) => State
+	<State>(recipe: (state: Draft<State>) => ValidRecipeReturnType<State>): (
+		state: State
+	) => State
+	<State, Args extends any[]>(
+		recipe: (state: Draft<State>, ...args: Args) => ValidRecipeReturnType<State>
+	): (state: State, ...args: Args) => State
+
+	/** Curried producer with initial state, infers recipe from initial state */
+	<State, Recipe extends Function>(
+		recipe: Recipe,
+		initialState: State
+	): InferCurriedFromInitialStateAndRecipe<State, Recipe, false>
+
+	/** Normal producer */
+	<Base, D = Draft<Base>>( // By using a default inferred D, rather than Draft<Base> in the recipe, we can override it.
+		base: Base,
+		recipe: (draft: D) => ValidRecipeReturnType<D>,
+		listener?: PatchListener
+	): Base
+
+	/** Promisified normal producer */
+	<Base, D = Draft<Base>>(
+		base: Base,
+		recipe: (draft: D) => Promise<ValidRecipeReturnType<D>>,
+		listener?: PatchListener
+	): Promise<Base>
+}
+
+/**
+ * Like `produce`, but instead of just returning the new state,
+ * a tuple is returned with [nextState, patches, inversePatches]
+ *
+ * Like produce, this function supports currying
+ */
+export interface IProduceWithPatches {
+	// Types copied from IProduce, wrapped with PatchesTuple
+	<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<Recipe, true>
+	<State, Recipe extends Function>(
+		recipe: Recipe,
+		initialState: State
+	): InferCurriedFromInitialStateAndRecipe<State, Recipe, true>
+	<Base, D = Draft<Base>>(
+		base: Base,
+		recipe: (draft: D) => ValidRecipeReturnType<D>,
+		listener?: PatchListener
+	): PatchesTuple<Base>
+	<Base, D = Draft<Base>>(
+		base: Base,
+		recipe: (draft: D) => Promise<ValidRecipeReturnType<D>>,
+		listener?: PatchListener
+	): Promise<PatchesTuple<Base>>
+}
+
+/**
+ * The type for `recipe function`
+ */
+export type Producer<T> = (draft: Draft<T>) => ValidRecipeReturnType<Draft<T>> | Promise<ValidRecipeReturnType<Draft<T>>>
+
+// Fixes #507: bili doesn't export the types of this file if there is no actual source in it..
+// hopefully it get's tree-shaken away for everyone :)
+export function never_used() {}
Index: frontend/node_modules/immer/src/types/types-internal.ts
===================================================================
--- frontend/node_modules/immer/src/types/types-internal.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/types/types-internal.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,55 @@
+import {
+	SetState,
+	ImmerScope,
+	ProxyObjectState,
+	ProxyArrayState,
+	ES5ObjectState,
+	ES5ArrayState,
+	MapState,
+	DRAFT_STATE
+} from "../internal"
+
+export type Objectish = AnyObject | AnyArray | AnyMap | AnySet
+export type ObjectishNoSet = AnyObject | AnyArray | AnyMap
+
+export type AnyObject = {[key: string]: any}
+export type AnyArray = Array<any>
+export type AnySet = Set<any>
+export type AnyMap = Map<any, any>
+
+export const enum Archtype {
+	Object,
+	Array,
+	Map,
+	Set
+}
+
+export const enum ProxyType {
+	ProxyObject,
+	ProxyArray,
+	Map,
+	Set,
+	ES5Object,
+	ES5Array
+}
+
+export interface ImmerBaseState {
+	parent_?: ImmerState
+	scope_: ImmerScope
+	modified_: boolean
+	finalized_: boolean
+	isManual_: boolean
+}
+
+export type ImmerState =
+	| ProxyObjectState
+	| ProxyArrayState
+	| ES5ObjectState
+	| ES5ArrayState
+	| MapState
+	| SetState
+
+// The _internal_ type used for drafts (not to be confused with Draft, which is public facing)
+export type Drafted<Base = any, T extends ImmerState = ImmerState> = {
+	[DRAFT_STATE]: T
+} & Base
Index: frontend/node_modules/immer/src/utils/common.ts
===================================================================
--- frontend/node_modules/immer/src/utils/common.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/utils/common.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,216 @@
+import {
+	DRAFT_STATE,
+	DRAFTABLE,
+	hasSet,
+	Objectish,
+	Drafted,
+	AnyObject,
+	AnyMap,
+	AnySet,
+	ImmerState,
+	hasMap,
+	Archtype,
+	die
+} from "../internal"
+
+/** Returns true if the given value is an Immer draft */
+/*#__PURE__*/
+export function isDraft(value: any): boolean {
+	return !!value && !!value[DRAFT_STATE]
+}
+
+/** Returns true if the given value can be drafted by Immer */
+/*#__PURE__*/
+export function isDraftable(value: any): boolean {
+	if (!value) return false
+	return (
+		isPlainObject(value) ||
+		Array.isArray(value) ||
+		!!value[DRAFTABLE] ||
+		!!value.constructor?.[DRAFTABLE] ||
+		isMap(value) ||
+		isSet(value)
+	)
+}
+
+const objectCtorString = Object.prototype.constructor.toString()
+/*#__PURE__*/
+export function isPlainObject(value: any): boolean {
+	if (!value || typeof value !== "object") return false
+	const proto = Object.getPrototypeOf(value)
+	if (proto === null) {
+		return true
+	}
+	const Ctor =
+		Object.hasOwnProperty.call(proto, "constructor") && proto.constructor
+
+	if (Ctor === Object) return true
+
+	return (
+		typeof Ctor == "function" &&
+		Function.toString.call(Ctor) === objectCtorString
+	)
+}
+
+/** Get the underlying object that is represented by the given draft */
+/*#__PURE__*/
+export function original<T>(value: T): T | undefined
+export function original(value: Drafted<any>): any {
+	if (!isDraft(value)) die(23, value)
+	return value[DRAFT_STATE].base_
+}
+
+/*#__PURE__*/
+export const ownKeys: (target: AnyObject) => PropertyKey[] =
+	typeof Reflect !== "undefined" && Reflect.ownKeys
+		? Reflect.ownKeys
+		: typeof Object.getOwnPropertySymbols !== "undefined"
+		? obj =>
+				Object.getOwnPropertyNames(obj).concat(
+					Object.getOwnPropertySymbols(obj) as any
+				)
+		: /* istanbul ignore next */ Object.getOwnPropertyNames
+
+export const getOwnPropertyDescriptors =
+	Object.getOwnPropertyDescriptors ||
+	function getOwnPropertyDescriptors(target: any) {
+		// Polyfill needed for Hermes and IE, see https://github.com/facebook/hermes/issues/274
+		const res: any = {}
+		ownKeys(target).forEach(key => {
+			res[key] = Object.getOwnPropertyDescriptor(target, key)
+		})
+		return res
+	}
+
+export function each<T extends Objectish>(
+	obj: T,
+	iter: (key: string | number, value: any, source: T) => void,
+	enumerableOnly?: boolean
+): void
+export function each(obj: any, iter: any, enumerableOnly = false) {
+	if (getArchtype(obj) === Archtype.Object) {
+		;(enumerableOnly ? Object.keys : ownKeys)(obj).forEach(key => {
+			if (!enumerableOnly || typeof key !== "symbol") iter(key, obj[key], obj)
+		})
+	} else {
+		obj.forEach((entry: any, index: any) => iter(index, entry, obj))
+	}
+}
+
+/*#__PURE__*/
+export function getArchtype(thing: any): Archtype {
+	/* istanbul ignore next */
+	const state: undefined | ImmerState = thing[DRAFT_STATE]
+	return state
+		? state.type_ > 3
+			? state.type_ - 4 // cause Object and Array map back from 4 and 5
+			: (state.type_ as any) // others are the same
+		: Array.isArray(thing)
+		? Archtype.Array
+		: isMap(thing)
+		? Archtype.Map
+		: isSet(thing)
+		? Archtype.Set
+		: Archtype.Object
+}
+
+/*#__PURE__*/
+export function has(thing: any, prop: PropertyKey): boolean {
+	return getArchtype(thing) === Archtype.Map
+		? thing.has(prop)
+		: Object.prototype.hasOwnProperty.call(thing, prop)
+}
+
+/*#__PURE__*/
+export function get(thing: AnyMap | AnyObject, prop: PropertyKey): any {
+	// @ts-ignore
+	return getArchtype(thing) === Archtype.Map ? thing.get(prop) : thing[prop]
+}
+
+/*#__PURE__*/
+export function set(thing: any, propOrOldValue: PropertyKey, value: any) {
+	const t = getArchtype(thing)
+	if (t === Archtype.Map) thing.set(propOrOldValue, value)
+	else if (t === Archtype.Set) {
+		thing.add(value)
+	} else thing[propOrOldValue] = value
+}
+
+/*#__PURE__*/
+export function is(x: any, y: any): boolean {
+	// From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js
+	if (x === y) {
+		return x !== 0 || 1 / x === 1 / y
+	} else {
+		return x !== x && y !== y
+	}
+}
+
+/*#__PURE__*/
+export function isMap(target: any): target is AnyMap {
+	return hasMap && target instanceof Map
+}
+
+/*#__PURE__*/
+export function isSet(target: any): target is AnySet {
+	return hasSet && target instanceof Set
+}
+/*#__PURE__*/
+export function latest(state: ImmerState): any {
+	return state.copy_ || state.base_
+}
+
+/*#__PURE__*/
+export function shallowCopy(base: any) {
+	if (Array.isArray(base)) return Array.prototype.slice.call(base)
+	const descriptors = getOwnPropertyDescriptors(base)
+	delete descriptors[DRAFT_STATE as any]
+	let keys = ownKeys(descriptors)
+	for (let i = 0; i < keys.length; i++) {
+		const key: any = keys[i]
+		const desc = descriptors[key]
+		if (desc.writable === false) {
+			desc.writable = true
+			desc.configurable = true
+		}
+		// like object.assign, we will read any _own_, get/set accessors. This helps in dealing
+		// with libraries that trap values, like mobx or vue
+		// unlike object.assign, non-enumerables will be copied as well
+		if (desc.get || desc.set)
+			descriptors[key] = {
+				configurable: true,
+				writable: true, // could live with !!desc.set as well here...
+				enumerable: desc.enumerable,
+				value: base[key]
+			}
+	}
+	return Object.create(Object.getPrototypeOf(base), descriptors)
+}
+
+/**
+ * Freezes draftable objects. Returns the original object.
+ * By default freezes shallowly, but if the second argument is `true` it will freeze recursively.
+ *
+ * @param obj
+ * @param deep
+ */
+export function freeze<T>(obj: T, deep?: boolean): T
+export function freeze<T>(obj: any, deep: boolean = false): T {
+	if (isFrozen(obj) || isDraft(obj) || !isDraftable(obj)) return obj
+	if (getArchtype(obj) > 1 /* Map or Set */) {
+		obj.set = obj.add = obj.clear = obj.delete = dontMutateFrozenCollections as any
+	}
+	Object.freeze(obj)
+	if (deep) each(obj, (key, value) => freeze(value, true), true)
+	return obj
+}
+
+function dontMutateFrozenCollections() {
+	die(2)
+}
+
+export function isFrozen(obj: any): boolean {
+	if (obj == null || typeof obj !== "object") return true
+	// See #600, IE dies on non-objects in Object.isFrozen
+	return Object.isFrozen(obj)
+}
Index: frontend/node_modules/immer/src/utils/env.ts
===================================================================
--- frontend/node_modules/immer/src/utils/env.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/utils/env.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,47 @@
+// Should be no imports here!
+
+// Some things that should be evaluated before all else...
+
+// We only want to know if non-polyfilled symbols are available
+const hasSymbol =
+	typeof Symbol !== "undefined" && typeof Symbol("x") === "symbol"
+export const hasMap = typeof Map !== "undefined"
+export const hasSet = typeof Set !== "undefined"
+export const hasProxies =
+	typeof Proxy !== "undefined" &&
+	typeof Proxy.revocable !== "undefined" &&
+	typeof Reflect !== "undefined"
+
+/**
+ * The sentinel value returned by producers to replace the draft with undefined.
+ */
+export const NOTHING: Nothing = hasSymbol
+	? Symbol.for("immer-nothing")
+	: ({["immer-nothing"]: true} as any)
+
+/**
+ * To let Immer treat your class instances as plain immutable objects
+ * (albeit with a custom prototype), you must define either an instance property
+ * or a static property on each of your custom classes.
+ *
+ * Otherwise, your class instance will never be drafted, which means it won't be
+ * safe to mutate in a produce callback.
+ */
+export const DRAFTABLE: unique symbol = hasSymbol
+	? Symbol.for("immer-draftable")
+	: ("__$immer_draftable" as any)
+
+export const DRAFT_STATE: unique symbol = hasSymbol
+	? Symbol.for("immer-state")
+	: ("__$immer_state" as any)
+
+// Even a polyfilled Symbol might provide Symbol.iterator
+export const iteratorSymbol: typeof Symbol.iterator =
+	(typeof Symbol != "undefined" && Symbol.iterator) || ("@@iterator" as any)
+
+/** Use a class type for `nothing` so its type is unique */
+export class Nothing {
+	// This lets us do `Exclude<T, Nothing>`
+	// @ts-ignore
+	private _!: unique symbol
+}
Index: frontend/node_modules/immer/src/utils/errors.ts
===================================================================
--- frontend/node_modules/immer/src/utils/errors.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/utils/errors.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,60 @@
+const errors = {
+	0: "Illegal state",
+	1: "Immer drafts cannot have computed properties",
+	2: "This object has been frozen and should not be mutated",
+	3(data: any) {
+		return (
+			"Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? " +
+			data
+		)
+	},
+	4: "An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.",
+	5: "Immer forbids circular references",
+	6: "The first or second argument to `produce` must be a function",
+	7: "The third argument to `produce` must be a function or undefined",
+	8: "First argument to `createDraft` must be a plain object, an array, or an immerable object",
+	9: "First argument to `finishDraft` must be a draft returned by `createDraft`",
+	10: "The given draft is already finalized",
+	11: "Object.defineProperty() cannot be used on an Immer draft",
+	12: "Object.setPrototypeOf() cannot be used on an Immer draft",
+	13: "Immer only supports deleting array indices",
+	14: "Immer only supports setting array indices and the 'length' property",
+	15(path: string) {
+		return "Cannot apply patch, path doesn't resolve: " + path
+	},
+	16: 'Sets cannot have "replace" patches.',
+	17(op: string) {
+		return "Unsupported patch operation: " + op
+	},
+	18(plugin: string) {
+		return `The plugin for '${plugin}' has not been loaded into Immer. To enable the plugin, import and call \`enable${plugin}()\` when initializing your application.`
+	},
+	20: "Cannot use proxies if Proxy, Proxy.revocable or Reflect are not available",
+	21(thing: string) {
+		return `produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '${thing}'`
+	},
+	22(thing: string) {
+		return `'current' expects a draft, got: ${thing}`
+	},
+	23(thing: string) {
+		return `'original' expects a draft, got: ${thing}`
+	},
+	24: "Patching reserved attributes like __proto__, prototype and constructor is not allowed"
+} as const
+
+export function die(error: keyof typeof errors, ...args: any[]): never {
+	if (__DEV__) {
+		const e = errors[error]
+		const msg = !e
+			? "unknown error nr: " + error
+			: typeof e === "function"
+			? e.apply(null, args as any)
+			: e
+		throw new Error(`[Immer] ${msg}`)
+	}
+	throw new Error(
+		`[Immer] minified error nr: ${error}${
+			args.length ? " " + args.map(s => `'${s}'`).join(",") : ""
+		}. Find the full error at: https://bit.ly/3cXEKWf`
+	)
+}
Index: frontend/node_modules/immer/src/utils/plugins.ts
===================================================================
--- frontend/node_modules/immer/src/utils/plugins.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/immer/src/utils/plugins.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,109 @@
+import {
+	ImmerState,
+	Patch,
+	ImmerScope,
+	Drafted,
+	AnyObject,
+	ImmerBaseState,
+	AnyMap,
+	AnySet,
+	ProxyType,
+	die
+} from "../internal"
+
+/** Plugin utilities */
+const plugins: {
+	Patches?: {
+		generatePatches_(
+			state: ImmerState,
+			basePath: PatchPath,
+			patches: Patch[],
+			inversePatches: Patch[]
+		): void
+		generateReplacementPatches_(
+			base: any,
+			replacement: any,
+			patches: Patch[],
+			inversePatches: Patch[]
+		): void
+		applyPatches_<T>(draft: T, patches: Patch[]): T
+	}
+	ES5?: {
+		willFinalizeES5_(scope: ImmerScope, result: any, isReplaced: boolean): void
+		createES5Proxy_<T>(
+			base: T,
+			parent?: ImmerState
+		): Drafted<T, ES5ObjectState | ES5ArrayState>
+		hasChanges_(state: ES5ArrayState | ES5ObjectState): boolean
+	}
+	MapSet?: {
+		proxyMap_<T extends AnyMap>(target: T, parent?: ImmerState): T
+		proxySet_<T extends AnySet>(target: T, parent?: ImmerState): T
+	}
+} = {}
+
+type Plugins = typeof plugins
+
+export function getPlugin<K extends keyof Plugins>(
+	pluginKey: K
+): Exclude<Plugins[K], undefined> {
+	const plugin = plugins[pluginKey]
+	if (!plugin) {
+		die(18, pluginKey)
+	}
+	// @ts-ignore
+	return plugin
+}
+
+export function loadPlugin<K extends keyof Plugins>(
+	pluginKey: K,
+	implementation: Plugins[K]
+): void {
+	if (!plugins[pluginKey]) plugins[pluginKey] = implementation
+}
+
+/** ES5 Plugin */
+
+interface ES5BaseState extends ImmerBaseState {
+	assigned_: {[key: string]: any}
+	parent_?: ImmerState
+	revoked_: boolean
+}
+
+export interface ES5ObjectState extends ES5BaseState {
+	type_: ProxyType.ES5Object
+	draft_: Drafted<AnyObject, ES5ObjectState>
+	base_: AnyObject
+	copy_: AnyObject | null
+}
+
+export interface ES5ArrayState extends ES5BaseState {
+	type_: ProxyType.ES5Array
+	draft_: Drafted<AnyObject, ES5ArrayState>
+	base_: any
+	copy_: any
+}
+
+/** Map / Set plugin */
+
+export interface MapState extends ImmerBaseState {
+	type_: ProxyType.Map
+	copy_: AnyMap | undefined
+	assigned_: Map<any, boolean> | undefined
+	base_: AnyMap
+	revoked_: boolean
+	draft_: Drafted<AnyMap, MapState>
+}
+
+export interface SetState extends ImmerBaseState {
+	type_: ProxyType.Set
+	copy_: AnySet | undefined
+	base_: AnySet
+	drafts_: Map<any, Drafted> // maps the original value to the draft value in the new set
+	revoked_: boolean
+	draft_: Drafted<AnySet, SetState>
+}
+
+/** Patches plugin */
+
+export type PatchPath = (string | number)[]
