-
Notifications
You must be signed in to change notification settings - Fork 124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Independent Editing #495
Comments
|
Might be worth doing a survey of all references to |
A |
Thought structure requirements:
Context overlap is the main difference from traditional graph structures. |
I'm willing to give up
|
I agree. I also came to a similar conclusion. If we don't want any recursive updates, we should completely remove the association of a context with thought too. A thought should only know its |
@raineorshine What about move operation? We would still have to recursively update all the lexeme related to descendants after a single move operation. Are we only scoping this to edit operation? |
We're changing the core thought structure, so it will affect all operations. Lexemes will point to a single |
I was thinking we should have a clear plan of action before starting an implementation. The goal is to transition the Here are some changes I believe will need to be made:
That is a large number of interrelated changes. I'm not sure how to divide-and-conquer. We should completely defer Looking forward to hearing your thoughts! |
Thanks for the details. I agree with all of these changes. I would like to add another change that seems necessary. We also need to generate context from a One way would be to add |
The context view shows all the contexts that the corresponding
Thoughts can have multiple parents. The visible parent of a thought is a product of the navigation path. If you get to a thought through a different path, it will have a different parent. This is why we build up the
Correct, we need to update |
The way we can get a thought in different paths is by using context view right ? |
@raineorshine In the current implementation, we also show where the lexemes appeared in different contexts in
Since now |
Yeah, that's less than ideal. The Context View is less useful that way. But we can't save a list of |
Yes, we cannot prevent recursive edits and also save a list of contexts. We should probably think about how this will affect the context view and sense-making. |
@shresthabijay After devising and scrapping several complicated solutions that never worked quite right, I now have evidence that there is a very simple solution... or evidence that my brain does not work properly after a certain hour 😅. Please ask questions and let me know if I am missing something obvious! Proposed SolutionThoughts = Doubly linked tree
When a thought is edited...
When a thought is moved...
|
@raineorshine I mentioned a similar solution up in the discussion. However, I didn't mention it as a doubly-linked tree.
As I mentioned above, the problem with this solution is that we may not have all the thoughts already loaded to traverse up the doubly-linked tree on the active context view. We will have to recursively traverse and pull from the remote or local source if we encounter any thought whose parent thought has not been loaded yet. Apart from that, I think this solution is fit for independent editing. |
Sorry, I was a little slow! I don't think I understood yet that Lexemes would handle multiple parents separately.
I think that is an acceptable cost. A thought that appears in
I am so relieved. It's a huge change, but it really seems like the right solution. We need to come up with a smart migration strategy to avoid trying to convert the app all at once, which would be a nearly impossible task.
I haven't thought yet about how this affects all the pull logic. Presumably we'll want to get it working with Redux state first, but we should at least think ahead to have a rough idea of what we're getting into. Thoughts? |
I completely agree. |
I think in brief we will have to do the following things in the pull:
I am not sure where we will pull the context for the |
I am still lacking a clear picture of the whole migration. Should we start by identifying such places ? |
I too think we should abstract away any component logic that breaks the separation of concern into a separate selector or hook. |
Interesting! I guess I expected
Yes, that needs to either be removed or replaced with a narrower slice. The Tutorial will need to be tested thoroughly with this change; we don't have any test coverage there. I would recommend ignoring this for now. It doesn't actually depend on any internal structure of the
Yes, that's a good intermediate abstraction. Ideally we abstract away the In summary, I would suggest using selectors like
Yes. Notably, it doesn't rely on the internal structure of
Yeah, makes sense. As long as we are doing syncing and reconciliation ourselves, those functions will have to access to the low-level thought structure. |
Decoupling Here were some of the changes mentioned earlier, but they are not presented in a sequential or comprehensive manner:
Here's my attempt at a more linear and comprehensive migration strategy: (Moved to issue description) Open to feedback 😅 |
It's a very good strategy. I would like to add something. After Previously we would have path conflicts in such situation and we needed rank in the
As you can see have three exact same paths Not anymore after independent editing! We won't need rank in the Also we would not need |
That's right! Some things get so much easier. Note that we may not want to change the type of |
Do you mean we change |
I wanted to confirm this. This is how I understand this step. We are changing the key of |
In Step 2, I was suggesting that we change the
Yes, exactly right. |
@raineorshine After we ditch step 2, then step 4 would not be possible. If Let's say we do both steps 2 and 4.
Then Somewhere down the line, we have the following changes:
In these steps we have to replace every change of step 2 by making child id to be I am thinking we should directly change make these changes instead of working on Step 2 and step 4. What do you think? |
@raineorshine Iterative changing of structures is causing confusion on migration. Specially fixing logic. Since much low-level logic is highly dependent on these structures I think it would be easy to first change the Parent, Path and Lexeme types at once, then deal with low-level changes. There could be possible changes in API. Most low-level functions that take Context param may have to be changed by the |
I will try to explain my reasoning. At some point we will have to Change But I am worried about all at once migration. So I will change the structures and only fix |
Ah, good point! I guess we can't delete Step 2. It won't have an effect at first, but it is necessary for the new
Since
Yes
I didn't include it in the migration strategy, but I think we should bracket Context View functionality initially until we get
I don't think this is necessary once we move
This is Step 8.
This will depend on the Regarding changing
My main goal with the iterative approach is to reduce the risk of making this large architectural change. There is the risk of hitting a dead-end and the risk of just getting stuck or slowing down too much when there many changes made at once. The combined approach reduces the total work while increasing risk and decreasing transparency. More changes that have to be done in single batches means less opportunity to define milestones and evaluate progress, and thus less transparency. Adding more work in order to decrease risk and gain transparency feels like a really good tradeoff to me with this kind of thing. I'm not convinced that the total amount of work is the most important thing to optimize for. |
I agree we need to make sure there's no confusion in our approach.
Using this above approximation of coupling patterns, we can see that Abstraction barriers allow us to insulate changes at a low-level from a higher-level. To me, it makes sense to first change the smaller number of low-level functions without affecting the large number of higher-level functions.
I don't think we want to change the API for low-level functions all at once. We can wrap the new thought structure so that consumers (higher-level functions) can call the low-level functions as-is. Then we can introduce temporary parallel types to migrate higher-level functions gradually.
I prefer this approach for small-to-medium sized changes and I understand the appeal. Having Typescript makes it very easy to take a "type-driven" approach. Change a type and watch it ripple throughout the system. Fix each type error and you'll have a working app again. I don't think this approach works as well for large changes with nontrivial changes at 100's of call sites. The problem is that the app won't compile while you're making all these changes to fix the type. I think 4-5 hours of work is about the limit for getting from A to B when it doesn't compile in between. Your feedback loop goes down, and you're working for long stretches with a mental model of the application. A long list of type errors may give you obvious next steps, but they can easily hide problems that may go unseen until the end. You can refactor 90% only to realize there is something that doesn't work in the last 10%. I think a "stepping stone" approach with multiple stopping points is a better approach for large-scale architectural changes. Temporary parallel types are another technique that allows for gradual change rather than all-at-once change. Typescript differentiated itself from other strongly typed systems by adding
We have the same goal in mind. Since the structure of
Do you mean change the internal implementation of |
I agree. I can see the problem with this approach now. It would be tedious to do and everything can go to waste if it doesn't work in the end. Thanks! |
I tried this just with |
Cool, good to know. We're getting closer! |
For this, we may need to change the API of Also in many places |
I think preserving the |
Yes, I guess that's unavoidable. At least it is a trivial change. It will require a little work if You can trivially reduce the number of
The We might have to add a backlink from
Yup, this is ultimately what we want everywhere, preserving ids from existing |
@raineorshine I think for every |
I just realized that we will still need an asynchronous recursive mechanism for deleting all descendants when we delete a thought. It should have resume support in case the connection to the server gets interrupted. |
I was thinking about this. We need to have a separate mechanism to handle this. |
Decouple thought identity from value so that thoughts can be edited without having to update all descendants.
← should be able to edit without changing all descendants
Migration strategy:
Child.id
required - This should still be done iteratively, as mentioned, since it has a large surface area. You can detectChild
nodes that are missing anid
and add them one by one.Child.id
from uuid to contextIndex key - Ultimately we need to unifyChild.id
andParent.id
. If we changeParent.id
to a uuid to matchChild.id
, it will breakhashContext
. Thus we changeChild.id
to matchParent.id
. They will be treated as separate universes at first, but this will make it easier to unify them later.(3b) Disable Context View - Here is another domain that can be disabled without affecting the initial conversion.
Parent.id
to an arbitrary uuid - This will change the thought structure and require changes in all low-level functions that are still enabled. Preserve their API to avoid any higher-level changes at this point. e.g.hashContext
will traverse from the HOME context to the desiredParent
and return its id instead of hashing theContext
. (This will be slow. We can optimize it later by also accepting aPath
, which will allow O(1)Parent
lookup withhead(path).id
.)Parent.context
- We begin removingContext
fromState
. We are anticipating that onceParent
entries become stable (i.e. unchanged with edits and ancestors),Contexts
for a given thought can change. ThusContext
shifts from a unique key to an ad hoc structure that is eventually replaced with simpler and more efficient access by id. Doing this now will make it easier to refactor recursive functionality sinceParents
will no longer have any reference to their ancestors.moveThought
,editThought
, anddeleteThought
, since they no longer depend on ancestors.state.thoughts
structure.Child
toParent
orParent.id
- That is, whereverChild
is used, replace it with aParent.id
(if persisted, e.g.Parent.children
) or a direct reference to aParent
object (in memory business logic). MoveChild.rank
toParent.rank
.ThoughtContext
toParent
orParent.id
- Same as the previous steps, but forLexeme.contexts
.Child
,Parent
, andThoughtContext
. We can begin refactoring and optimizing high-level functions which are performing some contortionist moves in order to access the new structure with their old logic. Luckily this can be done iteratively as they are not structural or functional changes. We can refactor systematically, or do some profiling to start with the most expensive functions.The text was updated successfully, but these errors were encountered: