DigitalGarden for TeamWork

Meta (triggered by wanking-smell below):

  • what problem am I trying to solve? For whom?
  • those prev bullets are too vague to convince someone, try again
    • building/running a business is a Complex/Wicket problem
    • every discussion can go into infinite detail, every "project" grows an infinite tree/web of tasks
    • turning discussions into actionable tasks (within milestones) grounds them, creates convergence
    • connecting detailed discussions and milestones to higher-level vision/strategy "notes" helps everyone stay aligned
  • what's the adoption-growth model?
    • would I use this myself?
      • my main complexity needs are for dayjob - My CollaborationWare History
      • I could use this to organize my own work
      • I could slide it into use when there's a messy discussion/decision to work on (but what SSO would i use?) (it's been an effort to get people to recognize that Slack-flat-thread doesn't work well).
  • then offer as hosted-SaaS plus open-source it?
    • this would probably stick with the "formal group controls a space"
    • would it be possible to build a cross-space view for a single user? (that's when it slides into TaskCloud model)

Some specific user scenarios

  • I dream up a project, I define a "space" for it
  • I write some docs in that space
    • some have parent nodes, others are children just of the "space" (at least to start)
    • any doc created by clicking the empty/new-page-frontlink of an existing page gets that page as its parent
  • I create a project deliverable/milestone - it's a child of a doc about it
  • I create a tree/graph of sub-tasks for that milestone
    • when at a task, I can hit "create subtask" to make a child
    • or I can create a raw task; and for any task, at any time, I can say "pick parent"
    • any task node can have any number of front-links and backlinks - are they all parents? No, you can give multiple parents, but not every relation is (automatically) the same.
    • are all parents equal, or is there a primary parent?
  • I do the same for a bunch of projects/notes/tasks
  • I have a to-do-list of open tasks off all levels across all those projects
  • I drag open tasks into priority order
    • there's a "move to top" or "make priority" button that bumps a task up after which I can drag it into order
    • that's my personal ranking, each follower has their own ranking value for a task
  • a given single-task view shows its task hierarchy "parentage"
    • if multiple parents, each path-up gets its own row
      • hmm but if each node up-stream has 2 parents, that tree grows crazy... which might be a strong argument for a primary parent
  • all these private projects are probably in a single name-space
  • then I decide I want to get other folks involved in 1 of them....
    • so I make a new space
    • and I move the "top" of that project to the new space...
    • all the children are now inherently moved to that space
    • which means you need to be a little more thoughtful about picking the node to spin off a new node from, since that becomes its parent
    • I can make that space read-public, write-invite
    • then I define a group for that space - I'm automatically a member, and the administrator
      • administrators are the only ones who can invite new members, or change any node rights
      • a group can have multiple administrators
      • maybe a smaller group of super-admins who have ultimate control (root space/group) (could think of this as just 1 but with redundance/backup)
  • I add people to the group page, and send invites (email? DM?).
    • when someone accepts, they set their WikiName/PetName
      • can I save my own PetName for someone? I suppose that could be auto-translated when I save a node.... but that raises weird uniqueness games...? That way lies craziness, so nope. In return, ask people to have "meaningful" PetNames for themselves?
      • a PetName is ascii/alphanumeric - no spaces or punctuation, so can be uses as handle/address @
    • as admin I can set each person to read-or-write-or-admin
  • SecondMember joins project/space
    • when you follow a node, you automatically follows its children. But for every node, you see a Follow checkbox, and you can turn it off for any node at any time. (You can always change your mind later.)
  • I assign a task to SecondMember... he she accepts
  • She adds a subtask to that task - because I'm following the original task I'm automatically following the subtask
    • the subtask gets the same ranking as its parent (or maybe you add .0000001 to parent value?)
  • She can add a private subtask - I won't see it
  • multiple people can have roles on a task: owner/assigner, assignee, follower (any role for a task can have multiple people)
    • hmm is there any value to distinguishing those roles, or do you just tag everyone? Probably worth distinguishing: this should be on my to-do-list, this is on my to-review list, vs I'm just keeping an eye on....
  • a task has a single Status at a time: InPrep, ReadyForWork, InProcess, InReview, Completed, Abandoned. (multiple sets of status are possible, controlled at the Space level?)
  • a task can have a flag: isReviewable? hasDeliverable? To distinguish "meatier" tasks for hidden/process tasks, esp as you "look up the hierarchy"?
  • I add a Progress Note to a task
    • can @-mention anyone in the body, and they get alerted-and-follow
  • I add a series of Progress Notes to a task
  • I can add a Progress Note that's a fork off a specific Progress Note that's not the just-past note - e.g. it's a graph
    • does this make Editing unnecessary? Or at least much less necessary/frequent?
  • is a question about a task a subtask, where answering/settling it makes it Complete?
    • what does this imply about other notes?
      • yes, any question becomes a task item
    • smells like need to be able to convert a note to a task
      • and vice versa?

Some possible design inspiration

Oct08'2023 going to start noodling on this


  • try stupid approach to Bootstrap update
    • move away all the CSS and JS files, except cherwell.css which I think was part of the stuff I grabbed from BenW
    • download bootstrap.min.css and bootstrap.bundle.min.js, put them in /static/
    • change various template/header files to refer to these new filenames
  • status: not horrible looking, but
    • no header, hamburger menu with login/etc
    • no side-by-side columns, just 1 long stream
    • not following the color scheme I had set up


  • bring back default.css which came from WithKnown. No change.
  • bring back a bunch of other mystery CSS files. No change.
  • options
    • start from scratch, try to build up what I want from bs-5-native recommendations
    • try to learn the old stuff better to adjust it
    • sounds like the 1st option is better!
  • realize I have base-vs-layout as semi-redundant template shells - clean that up to use just base, clean up tabs a bit, rip some stuff out... but still not enough...
  • meta, focus on node-page first, before dealing with FrontPage
  • ok, I'm going to start from nothing. Take Bootstrap shell of page, insert {% block body %}{% endblock %}, get super-plain page
  • bring over logic around title tag, good.
  • next - hamburger menu
    • want this one? Nope realize that involves a 3rd party plug-in, want to stay as "native" as possible.
    • looks like key is "navbar", ah this looks promising... paste it and it works.
    • merge in my version of the contents... works!
  • paste in my footer stuff, without lots of tag-shmutz... fine (though not centered)
  • paste in my header stuff (mostly metatags)... fine
  • check w3c HTML validator - fine.
  • could be finished with main page-template, but want some whitespace...
    • ah, just put <div class="container-fluid"> around the {% block body %}{% endblock %}, not bad


  • for FrontPage template, change 'span8' to 'col-8' (and equiv '4'), and now the multi-column view works great.
  • want to play with fonts, etc.
    • ok have to bring back an over-ride CSS file - in past generation it was default.css and it was very long. I'm going to use a flux.css and keep it as small as possible
  • put in my 2 link styles like a.wikilog - works!
  • FrontPage uses panel for each item - convert them to use card, also use card-header and card-body "properly"
    • -> hmm lots of borders and padding, and light gray background for each block's header - too busy?
    • yeah drop the card-header, that reduces a lot of it
  • do I want to over-ride the native-font-stack choice? I'll let it go for now, trying to stay as "lean" as possible...
  • realize my 2 search form fields in the header were using search-query, change them to form-control - they look a little nicer now, and are collapsed at a reasonable window-width...
    • uh-oh, suddenly notice the hamburger doesn't expand at all - is that from this fresh change, or did it happen earlier? Revert to search-query, it doesn't make it better, to revert-again to form-control....
    • ah, there was some broken-html-fragment way further down in the page, which was probably causing the JS file not to load. Now all good, including the hidden forms being the right width when expanded, etc. (Though if I widen the page, the forms are short and side-by-side.... play with a break? Put each in an <li>?)
  • (login to private space works, too)


  • hrm should I move straight to ToDoList stuff, or get TwinPages/SisterSites/WikiGraph working "locally", so I don't have the cross-domain JavaScript issue? Probably the latter....
  • do I have the table structure set up in any of my servers?
CREATE TABLE "spaces" (
    "name" varchar(32) NOT NULL UNIQUE,
    "page_pattern" varchar(128) NOT NULL,
    "all_pages_name" varchar(32) NOT NULL,
    "owner_user_id" integer);
CREATE TABLE "pages" (
    "space_name" varchar(32) NOT NULL REFERENCES "spaces" ("name"),
    "name" varchar(128) NOT NULL);
  • vs the tables in my main wikiflux/fluxgarden db:
CREATE TABLE public.spaces (
    id integer NOT NULL,
    path character varying(128),
    title character varying(128),
    owner_id integer,
    privacy_type character varying(32),
    created timestamp without time zone,
    modified timestamp without time zone
CREATE TABLE public.nodes (
    wiki_name_lower character varying(128) NOT NULL,
    wiki_name character varying(128) NOT NULL,
    space_id integer NOT NULL,
    body text,
    created timestamp without time zone,
    modified timestamp without time zone,
    title character varying(128),
    __ts_vector__ tsvector GENERATED ALWAYS AS (to_tsvector('english'::regconfig, (((title)::text || ' '::text) || body))) STORED
  • those schema made me pretty sure, but I confirmed I don't have this data merged in.
  • if I want to merge it, do I want to merge those similar tables together, or rename the WikiGraph tables? The latter would be "simpler".... What scenarios would make merging make more sense?
    • if I had so many FluxGarden customers that double-listing their pages in nodes and pages didn't make sense
    • if i planned to scrape SisterSites for their in-space connections
    • I don't think either of those are very likely....
  • further, I don't really feel like doing this work until it's really possible to finish (aka my new infrastructure is done). So I'm putting it aside.... back notes TeamGarden.....
  • what will I call to-do items? todos, tasks, actions ((2020-03-08) Taylor A Process Model Ontology)... I'm leaning toward tasks
  • what fields do I need to add? How many should get actual 'fields' vs being smushed into a json blob field?
    • parents: for notes, also
      • hmm, I have a mentions table (FrontLinks), which I use to generate BackLinks.... should I had a flag field on that table? In a sense the parent/child relation is a front/backlink which might not be explicit in the body.... so that record would be managed separately from the auto-dump/scrape upon page-edit, which kinda makes it feel like a separate table is better.... but...?
    • type or equiv to distinguish from notes?
      • Or does the presence of a field like 'status' indicate that?
      • Down the road, might a node have multiple types/templates?
    • status
    • watchers/participants/roles/permissions/priorities? {'bob':{'role':'assignee', 'priority':67, 'private':0}, 'jane':{'role':'creator', 'priority':2, 'private':0}}? (do I need a groups concept for people, for permissions?) (is there a permissions model I can steal from somewhere?)
  • conclusion: KISS/YAGNI: add a json-b field node_data for status and person/priority just (where I'm the only user), take it from there...)


  • SQL alter table nodes add column node_data JSONB;, from sqlalchemy import Column, Integer, String, Text, UnicodeText, DateTime, ForeignKey, Boolean, JSON, JSONB and node_data = Column(JSONB) -> ImportError: cannot import name 'JSONB' from 'sqlalchemy' (I'm running 1.3.23). Ah, have to use from sqlalchemy.dialects.postgresql import JSONB
  • UI to start creating a task?
    • Do I have to make it a Subtask of an existing node? My YAGNI says no, I'm going to rely on links/backlinks
    • Do I add some sort of field to the Edit form? Maybe a pulldown of status values, with null as a starting point, and if I pick a non-null value then that sets the value and tells me it's a task? Yeah, let's do that.... And contrary to earlier note I'll have status values be just ToDo, InProgress, and Done.
  • status = SelectField('Status:', choices=['ToDo', 'InProcess', 'Done'])... node_data=jsonify(status)
  • do "quick" add -> record saved but node_data is empty
  • needed to add to: node = Node(, page_url, title, body, created, modified, node_data)
  • try again -> Object of type Response is not JSON serializable.... 'node_data': <Response 7 bytes [200 OK]>
  • change to node_data=json.dumps({"status": status}) -> works? command-line query shows value as "{\"status\": \"InProcess\"}"
  • add if status=='Status': node_data = None -> "All fields required". Ah that's my own code, tied to form.errors... not a valid choice. Have to add 'Status' to the 'choices' list in so form will validate, then I can still over-ride that to set node_data = None. All good.
  • have created a few tasks. All of them are included on FrontPage/RecentChanges. All appear as Backlinks for the appropriate Parent/Note page.
  • how should my Tasks-list page work? I want
    • a prioritized list where I can drag/drop to reprioritize
    • what about new items with priority=null? Do I
      • automatically put them at the bottom of the list? Nope, because I know I end up with infinite tasks and will never find them?
        • hmm if nearly infinite will they be manageable? Will probably (a) have paginated (by 100) view, with (b) a "push to top" button for each task to jump it to the top...
      • automatically put them at the top of the list?
      • keep them in a 2nd list, sorted by create-date (or mod-date), with easy way to move them to top of priority list? Is it possible to drag an item from un-prioritized list to a slot in the prioritized list? Is that an htmx question?
        • see doc on wrapping the SortableJs with HTMX. See Sortable's "shared lists" example, that seems perfect. When the list is reordered via the Sortable.js drag-and-drop, the end event will be fired. htmx will then post the item ids in the new order to /items, to be persisted by the server. Does that mean I'll be re-writing a long list each time? What about the tasks after priority=100? (Note the HTMX example isn't of the shared-list version... Ah, discussion.
      • Hrm this makes me lean to "default at top of list" model.... how would that work? How would I define 'priority' so that I didn't have to rewrite the whole list each time? Maybe (a) start with a super-high-int (10^10 - 1?) as starting value, (b) always have the min(priority), and (c) set newItem.priority=minPriority-1? But then I have to worry about destination is between n and n+1.... or maybe ok that priority isn't unique, except my list will change... maybe use float/real instead of int, and split the difference?
    • further up I propose a JSON format for people+roles+priorities... do I bother with that now, default to me/assignee? Yeah, I think so, and use users.usename....
    • so node_data would look like {"status": "ToDo", "people": {"BillSeitz":{"role":"assignee", "priority":999999999, "private":0}}}
    • trying to mass-set the value via command-line (mac-postgres) for the 4 tasks I've created..., using this example: update nodes set node_data =jsonb_set(node_data, '{people}', '{"BillSeitz":{"role":"assignee", "priority":999999999, "private":0}}',true) where node_data is not null; -> cannot set path in scalar
      • this makes it sound even easier, but also doesn't work
      • could it be something specific to Mac/command-line, maybe with single-quotes vs double-quotes?
      • and/or maybe I'll start by wiping out the existing fields, because they don't smell right either....
      • I wiped out the existing field data
      • now the update command above acts like it worked (UPDATE 6), but a select shows no data


  • new plan, do some temporary hack in edit-node UI/code to push in the full JSON I want
    • first, display it! - Done, all appear to be empty
  • realize my sample little dict above was short a final }
  • add hard-coded dict to my node_edit() logic
  • load/view a task page, edit, tweak body, save -> page says All fields required, console says
Message: 'form errors'
Arguments: ("{'status': ['Not a valid choice']}",)
  • ah, it's because my edit form has the Status pulldown only on Create not Update, so form-validation fails
  • copy Status-pulldown code to other case in edit-form, view, edit, save -> save is successful, but no data set


  • oops I didn't have my hack-set-jsonb value code in the right place, now I do.
  • nope, logger says UPDATE command doesn't include node_data
  • is this the issue? I need to involve sqlalchemy-json? And for jsonb provide a different backing type. This is done by using the utility function mutable_json_type.


  • make the couple changes to for mutable
  • also use copy() as a hack
  • -> nope the SqlAlchemy UPDATE still doesn't include the field, gonna have to add the other library
  • install library, change code -> still doesn't work
  • try deepcopy -> UnboundLocalError: local variable 'node_data_dict' referenced before assignment
  • that just confirms that I'm creating the variable from scratch here, so why is all this craziness necessary in the first place? I'm getting that smell that it's not the mutation-detection that's the issue....
  • omg i'm an idiot, need it to be node.node_data=json.dumps(node_data_dict).
    except... -> 'super' object has no attribute 'coerce'


  • clarified the above error happens inside sqlalchemy-json, not the json.dumps() part
    • this was an issue in 2018, and still not resolved?
    • I tried SQL update nodes set node_data='{}' where wiki_name='SetsaveStatusWhenCreatingTask' then tried this again, same issue. (Still not sure whether that SQL is correct since I didn't use jsonb_set()
    • This issue seems too obvious, I suspect I have some other issue that triggers this edge case...
    • Tried SQL again update nodes set node_data='{"status": "ToDo"}' same result upon edit/save... in debugger I can see that node.node_data = sqlalchemy_json.NestedMutableDict({'status': 'ToDo'})
    • Nov26 post query
    • Dec13 no response/action over there. Try a tweet.

Edited:    |       |    Search Twitter for discussion