- Published on
Benefits of a text-based (web) app
- Authors
- Name
- Rob Koch
- @rob__koch
The functionality of markwhen starts from the source: the input text.
When the user types something, the text is (re)parsed, and the resulting timeline information is fed to the view to display.
The user can then edit the raw text via the editor, or perform actions via the UI that will edit the text on their behalf, causing the cycle to continue.
It's all in the text
You only have one source of truth - the text. Everything else is a transformation thereof.
If we expand some of the boxes in our diagram, we can see that we have separate steps, each of which takes input from the last and does its own work on top of it. Because I'm using vue, these are computed
properties, which is perfect as they only update when their dependencies change.
pageStore.ts
This here is an example of one of the (pinia) stores that does some transformations. For readers more familiar with react and redux, pinia is like redux except it isn't fucking convoluted and needlessly complex.
You can see that we're relying on outputs from the markwhenStore
to produce new outputs which will be further utilized downstream.
Note the lever 🕹 emoji, which we will come back to.
import { defineStore } from 'pinia'
import { computed, ref, watch, watchEffect } from 'vue'
import { useMarkwhenStore } from './markwhenStore'
export const usePageStore = defineStore('page', () => {
const pageIndex = ref<number>(0)
const markwhenStore = useMarkwhenStore()
/* 🕹 */ const setPageIndex = (index: number) => (pageIndex.value = index)
const pageTimeline = computed(() => markwhenStore.timelines[pageIndex.value])
const pageTimelineMetadata = computed(() => pageTimeline.value.metadata)
const tags = computed(() => pageTimeline.value.tags)
const pageTimelineString = computed(() =>
markwhenStore.rawTimelineString.slice(
pageTimelineMetadata.value.startStringIndex,
pageTimelineMetadata.value.endStringIndex
)
)
return {
// state
pageIndex,
// actions
setPageIndex,
// getters
pageTimeline,
pageTimelineMetadata,
pageTimelineString,
tags
}
})
🕹 Levers
What's nice about this setup is that you can allow users' to have levers into each of these transformations, and they will propagate nicely downstream, without affecting whatever is upstream.
In the Set the current page
example, the variable pageIndex
from the pageStore
is changed, which updates the pageTimeline
, pageTimelineMetadata
, and tags
, which feed into the next transform that needs them.
What this gives us
A nice separation of concerns, clear, explicit dependencies, and modularity for introducing new features. Either a new feature fits nicely into what a transform is already doing, or maybe we need a new one and then we just have to choose where to insert it.
What is actually being described
Maybe I'm just trivially describing reactive frontends like vue/svelte/react in general. Though even with those frameworks it can be tempting if not necessary to work outside the nice loop that we have drawn.
To me it feels like functional programming; simple inputs and outputs. State is a bitch, and if you have a nice flow that definitively starts somewhere (the text) versus imperatively trying to reason stuff out, things tend to fall into place nicely (obviously this is a bit disingenuous, in both cases things are reasoned about, but it doesn't feel like it as much in the former case).