When displaying the schedule, scroll it such that the now line is on the
left to make what is displayed by default the most immediately useful
information.
Pull out the list of events into its own page sorted by name and show
the event slots in chronological order on the schedule page, with past
slots hidden by default. This makes the content underneath the schedule
the most immediately useful to have in the moment, while the full list
is kept separately and in a predictable order.
It used to be that the assigned property was not present for clients who
are not crew, but this changed with the client state refactor. It makes
more sense to only show the crew field if there are any crew present.
When async components are added dynamically to the tree via v-for list
that change their order and position gets messed up. I am not sure what
causes this, so I will just work around the issue for now and not use
async components.
Components that need async data loaded will instead depend on the parent
page fetching this data during its setup.
The nuxi typecheck command complains about type only imports that are
not declared as such, but the VsCode environment does not. There's
probably a missmatch somewhere in the configuration for Nuxt that I'm
not going to dig into. Workaround this issue for now by setting the
option in the tsconfig.json file for the server.
Use the authentication method system for the demo login and the
generated accounts. This makes it possible to toggle it off on
production systems as these shouldn't have it enabled at all.
When a user browses to a page from another site, for example via a
shared link we want the browser to send the session cookie so that
the page renders as the user and not confusingly being logged out.
This may cause CSRF vulenrabilities, later work to add CSRF tokens
should be considered.
Add the concept of authentication methods that authenticate an account
where using the telegram login widget is one such method. If a login is
done with an authentication method that's not associated with any
account the session ends up with the data from the authentication
method in order to allow registering a new account with the
authentication method.
This has to be stored on the session as otherwise it wouldn't be
possible to implement authentication methods such as OAuth2 that takes
the user to a third-party site and then redirects the browser back.
If a session is rotate in the middle of a server side rendering then
some random portions of requests made on the server side will fail with
a session taken error as the server is not going to update the cookies
of the client during these requests.
To avoid this pitfall extend the expiry time of sessions to be 10
seconds after the session has been rotated. This is accomplished by
introducing a new timestamp on sessions called the rotateAt at time
alongside the expiresAt time. Sessions used after rotateAt that haven't
been rotated get rotated into a new session and the existing session
gets the expiresAt time set to 10 seconds in the future. Sessions that
are past the expiredAt time have no access.
This makes the logic around session expiry simpler, and also makes it
possible to audit when a session got rotated, and to mark sessions as
expired without a chance to rotate to a new session without having to
resort to a finished flag.
After the change to converting users to tombstones instead of removing
them from the database several places would accidentally use deleted
user accounts instead of ignoring them.
If an anonymous session is detected as taken the logic preventing the
session from being accidentally deleted would also prevent the user from
recovering from a taken anonymous session.
When a session expires close any event streams that have been opened
with that session. This prevents an attacker with a leaked session
cookie from opening a stream and receiving updates indefinitely without
being detected.
By sending the session the event stream is opened with when the stream
is established this closure on session expiry also serves as a way for
a user agent to be notified whenever its own access level changes.
In order to minimise the window of opportunity to steal a session,
automatically rotate it onto a new session on a frequent basis. This
makes a session cookie older than the automatic rollover time less
likely to grant access and more likely to be detected.
Should a stolen session cookie get rotated while the attacker is using
it, the user will be notificed that their session has been taken the
next time they open the app if the user re-visits the website before the
session is discarded.
I've settled on a short-eared owl (Asio Flammeus) to serve as the mascot
and icon for Owltide. This is not a settled and final decision, but
rather a branding that's good enough for now.
To create the mascot I sketched some owls that I have saved to
/assets/mascot/owl-sketch.png and then selected one I liked. Going
clockwise from the top right the sketches were based on the reference
photos [1], [2], and [3] found by searching for "owl" on Wikimedia
Commons. I then lightly refined it to work as a small icon.
To avoid confusing terms that only make sense for software the artworks
are licensed under the Creative Commons Attribution-ShareAlike 4.0
License. This has a similar spirit to the AGPL and ensures that should
improvements be made upon them they can be incorporated in the project.
[1]: https://commons.wikimedia.org/wiki/File:Athene_cunicularia_-near_Goiania,_Goias,_Brazil-8_edit.jpg
[2]: https://commons.wikimedia.org/wiki/File:Hibou_des_marais.jpg
[3]: https://commons.wikimedia.org/wiki/File:Uhu-muc.jpg
The name is inspired by the watchful owl perching from the tree tops
with complete overview of all that's going on combined with -tide in
the sense it's used for in words like summertide and eastertide.
I firmly believe in free software.
The application I'm making here have capabilities that I've not seen in
any system. It presents itself as an opportunity to collaborate on a
tool that serves the people rather than corporations. Whose incentives
are to help people rather, not make the most money. And whose terms
ensure that these freedoms and incentives cannot be taken back or
subverted.
I license this software under the AGPL.
Allow a shift to have no role associated with it in order to simplify
conflict resolution around situations like a shift being created while
the role it was assoiated with was deleted. This also allows for shifts
that are freestanding to be created in case having a role doesn't make
sense for it.
Add a save dialog at the bottom of the screen that is present whenever
there are unsaved changes. This dialog provides a diff between the
client and server state so that the user can easily confirm the changes
they are about to make are the correct changes before applying them to
the server.
Make sure that all of the entities generated in the demo schedule are
updated in the past. Otherwise updates for them will end up rejected
for being before the current entity's updateAt property.
This also sets the origin for the schedule to be yesterday to make it
easiser to demo the schedule between friday and tuesday.
Array.sort() sorts by UTF-16 code points even when the items in the
array are numbers. Fix the schedule breaking when events cross
different powers of 10 in Unix time which caused the junctions to no
longer be sorted by the numeric value of their Unix time.
When modifying the set instead of replacing it with a new set the change
detection logic in Vue.js doesn't properly propagate the change, causing
certain computed properties that depend on them to go stale.
Fix by creating a new set here, which will emit a modelValue:update
event which will propagate through the v-model bindings.
Add routes and admin panel elements for creating a database backup,
restoring from a backup, deleting the existing schedule, and replacing
the database with the demo schedule. These server as crude ways to
manage the data stored in the system.
Instead of having to type in exactly the name of events or shifts and
then hope you remembered it right, replace these interactions with the
custom select component that gives a complete list of the available
choices and allows quickly searching for the right one.
Vue's proxy handling interferres with the new Set methods causing them
to throw a TypeError when used on Set objects that are replaced by
proxies. Workaround by dropping the proxy when using these.
See https://github.com/vuejs/core/issues/11398
When editing the slots of events and shifts there are certain situations
where the event or shift a slot should belong to becomes unclear or
difficult to reliably assign. For example when adding a new slot in the
UI it may be desirable to do so before the user has input the event
or shift the slot should belong to.
In these cases, not being able to store the slot into the schedule makes
the UI logic needlessly complicated. Allow slots to be added that do
not have its assiated relation linked up to make editing and handling
such scenarios easier.
The selection of locations, events, roles, shifts and users using the
native <select> element makes for awkward and difficult interactions.
Add an alternative select control that fixes the issues with the poor
handling and navigation of the control when having many options.
The custom select component can handle the selection of either one or
many entity from a ClientMap of entiteis with a name. Typing into the
text box searches the entities by name, arrow keys can navigate and
enter confirms the chosen entity by toggling it's presence in the
selection.
To avoid hydration matches string operations need to do the same thing
on the client and server. Add utilities for performing sorting and fuzzy
matching of strings that uses the account's locale.
Instead of merging overlapping events and shifts when displaying them in
the timetable which causes a very confusing display, add new rows when
events overlap so that each event can be fully displayed without any
overlapping in the table.
Fix userStore attempting to fetch the users list when it's either not
logged in or not allowed to fetch the users, causing the schedule page
to not render.