Skip to content

Conversation

@scrive-cat-bot
Copy link
Collaborator

This PR contains solution from the candidate fierce-rabbit

@kraklin
Copy link
Contributor

kraklin commented Nov 28, 2025

Is this ready for review or are there going to be more changes?

npm ci
npm start
```
test stuff
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is here from testing out the scrive-cat deployment and accidentally commit to master. Please ignore this line.

@scrive-cat-bot
Copy link
Collaborator Author

Ready for review I would say. Of course, there is still space for improvement but it's always. :)

@kraklin kraklin added the ready for review Marks candidate task to be ready for review label Dec 1, 2025
src/Main.elm Outdated
Value.addError "Name is required" nameValue

else if List.any (.name >> Editable.valueFromEditable >> Value.fromValue >> (==) nameString) model.tags then
Value.addError "Duplicate name" nameValue
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing your validation error messages in the Model is a bad idea, they are redundant information.

Case in point:

  • Enter a duplicate Tag Name (an error hint will correctly display)
  • Delete the duplicate tag
  • The "duplicate" error hint does not disappear

This approach makes your update function more complicated than it needs to be and more prone to errors.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! Just to clarify, in this code globalError only stores JSON decoding errors.
Validation errors for inputs (like duplicate tag names) are already handled directly in their Value, so they dynamically update without storing redundant information in the Model.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! Just to clarify, in this code globalError only stores JSON decoding errors.
Validation errors for inputs (like duplicate tag names) are already handled directly in their Value, so they dynamically update without storing redundant information in the Model.
But for the scenario that you mentioned, I added validation also for ClickedRemoveTag message.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both globalError and the error stored inside a Value are redundant, because they can be derived from information you have already.

In the case of globalError, its value can always be derived from the JSON.
We could say that we are pretending we got that data from a remote server, but in that case I think it would be better to store the Result produced by Json.decodeString.

In the case of Value, we can derive the error simply from the Model.
We just need the input value and the list of the other tags to know to know whether it is duplicate or not.
Storing the error message inside Value is redundant and results in a (minor, in this case) bug.

src/Main.elm Outdated
emptyNewTagForm : Tag
emptyNewTagForm =
{ name = Value.toValue "" |> Editable.valueToEditable
, value = Value.toValue "" |> Editable.valueToEditable
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads a bit weird. Value.toValue sounds like I take a Value and transform it into a Value.
Perhaps Value.fromString?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function works for any type of value, not just String. I could rename it to something like Value.validValue to make that clearer.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't Value.valid be enough?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function works for any type of value, not just String. I could rename it to something like Value.validValue to make that clearer

src/Main.elm Outdated
updatedTags =
List.map
(\tag ->
if (Editable.valueFromEditable tag.name |> Value.fromValue) == nameString then
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Editable.toValue would read better.

src/Main.elm Outdated
---- VIEW ----
ClickedRemoveTag nameToRemove ->
( { model
| tags = List.filter (.name >> Editable.valueFromEditable >> Value.fromValue >> (/=) nameToRemove) model.tags
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the systematic approach, but OTOH we need to keep unwrapping (twice!) and wrapping these Strings and I'm not sure there's a clear benefit, it feels like it may be perhaps a bit overengineered?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your constrain and you're right - in such a simple project it's not necessary.
I mainly used this approach to demonstrate that I’m comfortable working with more advanced data structures and patterns, even if they aren’t strictly required here.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. =)

subheader : String -> Html msg
subheader text =
Html.span [ Attrs.class "p-2 text-2xl font-extrabold text-slate-800" ]
Html.span [ Attrs.class "p-2 text-2xl font-extrabold text-[#1A1A1A]" ]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you decide to use color literals instead than constants?

Copy link
Contributor

@kraklin kraklin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the solution to our hiring task. I quite like it. Splitting the UI parts away to separate modules in Shared is a nice touch for sure.

UI is clear and navigable by keyboard with usage of aria tags and/or titles for image icons - nice :) I'd add the possibility to enter the tag from the form by hitting Enter as I was doing that repeatedly when trying to navigate only by keyboard.

Code reads mostly good, there is one or two places that feels too unnecessarily nested to me.

I left some suggestions and questions in the comments. Can you expand on them there? 🙏

Comment on lines 7 to 8
view : String -> String -> Html msg
view id tooltipText =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you choose the view to have String -> String type? Do you know why and under what circumstances it is a problem and how to mitigate it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, String types can be accidentally swapped, so it's better to use record in cases like this. I will fix it.

Comment on lines +8 to +13
type IconType
= Add
| Edit
| Checkmark
| Refresh
| Remove
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you go with type variant and not exposing the icons as separate functions? What are the benefits of this approach? What are the drawbacks?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose to use a type variant IconType because it's simple and consistent. It allows calling Icon.view Icon.Add uniformly and easily mapping over lists of icons.
The main drawback is that every render requires a case expression, and it’s slightly less flexible if we need customization.
Using separate functions for each icon would avoid the case but make it harder to dynamically work with collections of icons.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my solution I used inspiration from elm components pattern (https://elm.land/concepts/components.html) what is approach that makes sense to me :)

Comment on lines 9 to 11
toValue : a -> Value a
toValue =
Valid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You expect the value to be valid straight away during the conversion to Value. What if we'd like to validate this value based on some validators? Would you do some changes to the Value module in such case or would you keep it as it is?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose to have toValue create a Valid value directly because my goal was mainly to demonstrate the Value module.
I didn’t want to go into unnecessary details of full validation logic for this task, but the module could be extended with validators if needed.

Comment on lines 34 to 51
isValid : Value a -> Bool
isValid value =
case value of
Invalid _ _ ->
False

Valid _ ->
True


isInvalid : Value a -> Bool
isInvalid value =
case value of
Invalid _ _ ->
True

Valid _ ->
False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO this could be simplified:

isInvalid =
   not << isValid

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I overlooked this one - thanks for pointing it out :)

Comment on lines 51 to 52
updateIfEditable : Editable a -> a -> Editable a
updateIfEditable editable newValue =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd IMHO switch the order of parameters for better composability to a -> Editable a -> Editable a. Why did you use this order here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, switching the parameters would make it easier to use this function with mapping, and it also aligns better with Elm conventions.

( tooltipId, tooltipAttributes ) =
let
tooltipId_ =
"tooltip-" ++ Maybe.withDefault "button" label
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under which circumstances this can backfire? And how would you mitigate the problem?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which exact part of the code do you mean? I’ve committed a few times in the meantime, so I’m not sure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I mean the fact that you are creating element ID from label. What might be the problem here?

src/Main.elm Outdated
Value.addError "Name is required" nameValue

else if List.any (.name >> Editable.valueFromEditable >> Value.fromValue >> (==) nameString) model.tags then
Value.addError "Duplicate name" nameValue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nesting of let .. in inside let .. in is a codesmell to me at it might be quite challenging to follow which value is from where and it adds complexity to overall code readability. Would it be possible to refactor it out? How?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall lots is going on inside upadte function. Do you have an idea how to make it more readable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that this can make the code harder to read. One solution could be to use a single let...in block, or better, to extract parts of the logic into separate helper functions. In a real project, I would likely create a dedicated module for validators to keep the code clean and modular.


type alias Model =
{}
{ tags : List Tag
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you choose to use List here instead of e.g. Dict? Can you elaborate?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose List because the number of tags is expected to be small and operations like adding, removing, or iterating over them are straightforward.
Using a Dict could be beneficial if we needed fast lookups by key or expected a very large collection, but for this task a List keeps the code simpler and more readable.

src/Main.elm Outdated
Comment on lines 364 to 371
]
++ (if Editable.isEditable editable then
[]

else
[ Attrs.attribute "readonly" "true" ]
)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is Html.Attributes.Extra.attributeIf in Html.Extra package that is quite helpful with these conditional attributes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know this but I love it, thanks.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very useful and elegant, thanks!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love it, thank you :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love it, thanks :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love this, thanks for sharing :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love this, thank you :)

src/Main.elm Outdated
Comment on lines 373 to 380
, if Editable.isEditable editable then
Html.text ""

else
Html.div
[ Attrs.class "pointer-events-none absolute inset-0 flex items-center px-3 text-gray-700"
]
[ Html.text valueString ]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is Html.Extra.viewIf in the Html.Extra package that shines in situations like these.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very useful and elegant, thanks!

src/Main.elm Outdated
let
newTagFormView =
Html.div [ Attrs.class "mb-6 grid grid-cols-1 md:grid-cols-[2fr_2fr_auto] gap-3 items-center w-full max-w-full" ]
[ inputField "Tag Name *" model.newTagForm.name UpdateNewName
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no explanation of what "*" means.
I would but a more explicit "(required)".

]
>> List.singleton
)
>> Maybe.withDefault []
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like a case..of here would be more readable.

src/Main.elm Outdated

else
[ Attrs.attribute "readonly" "true" ]
)
Copy link

@xarvh xarvh Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure of the accessibility implications of creating an <input> field just to set it to readonly.

, Attrs.attribute "aria-label" (Maybe.withDefault "Button" label)
]
++ tooltipAttributes
++ [ Events.onClick onClickMsg ]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this added by itself rather than together with the first List of attributes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I’ve already pushed a few changes, so I’m not sure which part of the code this comment is about.

, SvgAttrs.d "M6 18L18 6M6 6l12 12"
]
[]
]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the advantage of having all the icons in a single function rather than each their own constant?
(ie, Shared.Icon.add, Shared.Icon.edit... and so on)

Copy link

@xarvh xarvh Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Nevermind, I see Tomas asked the same question.)

@xarvh
Copy link

xarvh commented Dec 1, 2025

The Shared modules seem to be designed to have their own code clean rather than to allow the user code to be clean.
ie: instead of hiding complexity inside themselves, they offload it to the user code.

The UI feels good, is clear and easy to interact with.
The layout is very nice.

There is a bit of overengineering, but I'll write it off as the candidate having fun with Elm, which is a plus. =)

@scrive scrive deleted a comment from scrive-cat-bot Dec 1, 2025
@scrive scrive deleted a comment from scrive-cat-bot Dec 1, 2025
@scrive scrive deleted a comment from scrive-cat-bot Dec 1, 2025
@scrive scrive deleted a comment from scrive-cat-bot Dec 1, 2025
@scrive scrive deleted a comment from scrive-cat-bot Dec 1, 2025
@scrive scrive deleted a comment from scrive-cat-bot Dec 1, 2025
@scrive-cat-bot
Copy link
Collaborator Author

Thanks! I see what you mean about the Shared modules. I agree it adds some complexity to the user code, but I wanted to show working with reusable components. I’m glad that you like the visual part!
And thank you all for your feedback, I really appreciate it :)

@xarvh
Copy link

xarvh commented Dec 4, 2025

No worries.
Also, sorry for the barrage of comments.
My intention was to poke you and see how you think, but rereading our whole interaction and given that you are a candidate I fear my writing came off as overly judgmental.

@scrive-cat-bot
Copy link
Collaborator Author

No worries at all — I didn’t take it as overly critical.
One thing I forgot to mention: for Shared.Icon I used the components pattern (https://elm.land/concepts/components.html
), because that approach makes sense to me.

@scrive-cat-bot scrive-cat-bot force-pushed the anonymized-fierce-rabbit branch from 3c8379a to 006a1e7 Compare December 5, 2025 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for review Marks candidate task to be ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants